Files
proxmox/docs/07-ccip/CW_DEPLOY_AND_WIRE_RUNBOOK.md
defiQUG 7ac74f432b chore: sync docs, config schemas, scripts, and meta task alignment
- Institutional / JVMTM / reserve-provenance / GRU transport + standards JSON
- Validation and verify scripts (Blockscout labels, x402, GRU preflight, P1 local path)
- Wormhole wiring in AGENTS, MCP_SETUP, MASTER_INDEX, 04-configuration README
- Meta docs, integration gaps, live verification log, architecture updates
- CI validate-config workflow updates

Operator/LAN items, submodule working trees, and public token-aggregation edge
routes remain follow-up (see TODOS_CONSOLIDATED P1).

Made-with: Cursor
2026-03-31 22:31:39 -07:00

358 lines
17 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Runbook: Deploy cW* and Wire Config
**Created:** 2026-02-27
**Purpose:** Steps to deploy cWUSDT/cWUSDC on a chain, set `CW_BRIDGE_<CHAIN>`, update token-mapping, and verify roles. Covers Phase D and E from [CW_BRIDGE_TASK_LIST.md](../00-meta/CW_BRIDGE_TASK_LIST.md).
---
## Prerequisites
- `smom-dbis-138/.env` has `CW_BRIDGE_<CHAIN>` set (already done from deployed bridge suite for Mainnet, Cronos, BSC, Polygon, Gnosis, Avalanche, Base, Arbitrum, Optimism).
- For **cross-chain mint** to work, the bridge at that address must either be extended to mint cW* in `ccipReceive` or you must deploy a dedicated cW* receiver (e.g. TwoWayTokenBridgeL2) and point `CW_BRIDGE_<CHAIN>` to it; see [CW_BRIDGE_APPROACH.md](CW_BRIDGE_APPROACH.md).
- RPC URL and `PRIVATE_KEY` for the target chain(s).
### Optional hard-peg deployment knobs
`DeployCWTokens.s.sol` now supports:
- `CW_STRICT_MODE=1` — revoke deployer `MINTER_ROLE` / `BURNER_ROLE` after granting the bridge
- `CW_GOVERNANCE_ADMIN=0x...` — grant `DEFAULT_ADMIN_ROLE` to governance; in strict mode the deployer admin role is revoked when this is set
- `CW_FREEZE_OPERATIONAL_ROLES=1` — freeze future `MINTER_ROLE` / `BURNER_ROLE` changes on the token after setup
For production hard-peg rollouts, use at least `CW_STRICT_MODE=1`.
### Strict bridge hard-peg requirements
If you are using [`CWMultiTokenBridgeL1.sol`](../../smom-dbis-138/contracts/bridge/CWMultiTokenBridgeL1.sol) and [`CWMultiTokenBridgeL2.sol`](../../smom-dbis-138/contracts/bridge/CWMultiTokenBridgeL2.sol) for `cWUSDC` / `cWUSDT`, strict mode now also means:
- L1 must explicitly allowlist the canonical token with `configureSupportedCanonicalToken(token, true)`
- L1 should set a per-destination ceiling with `setMaxOutstanding(token, chainSelector, amount)` unless you intentionally want unlimited capacity
- L1 should attach [`CWReserveVerifier.sol`](../../smom-dbis-138/contracts/bridge/integration/CWReserveVerifier.sol) so new outbound wraps are blocked when canonical backing is unsafe
- L2 token pairs and destination peers should be frozen after wiring with `freezeTokenPair(canonicalToken)` and `freezeDestination(chainSelector)`
- Admin withdrawal of supported canonical escrow is blocked while funds are locked, so “rescue” flows must use the bridge or pause process instead of `withdrawToken`
Operational note: the verifier gates new `lockAndSend` mints. Return `ccipReceive` releases on Chain 138 are intentionally left live so users are not trapped in wrapped positions during a reserve incident.
---
## Phase D: Deploy cW* and wire config
**One-command helper (from repo root):**
`./scripts/deployment/run-cw-remaining-steps.sh` runs a dry-run and `--update-mapping` by default. Use `--deploy` to broadcast, then set CWUSDT_*/CWUSDC_* in .env from output and run again with `--update-mapping` (or run `--update-mapping` after editing .env). Use `--verify` to check MINTER/BURNER roles per chain and `--verify-hard-peg` to inspect the Avalanche hard-peg bridge state (`supportedCanonicalToken`, `maxOutstanding`, verifier attachment/config, and L2 freeze flags).
### D1. Run cW* deploy
**All supported chains:**
```bash
cd smom-dbis-138
./scripts/deployment/deploy-tokens-and-weth-all-chains-skip-canonical.sh --deploy-cw
```
Or from repo root (runs same deploy, then can run update-mapping/verify):
```bash
./scripts/deployment/run-cw-remaining-steps.sh --deploy
```
**Single chain (e.g. BSC 56):**
```bash
cd smom-dbis-138
source .env
# Use the per-chain bridge; script will pick CW_BRIDGE_BSC when running for chain 56
CW_BRIDGE_ADDRESS="$CW_BRIDGE_BSC" forge script script/deploy/DeployCWTokens.s.sol:DeployCWTokens \
--rpc-url "$BSC_RPC_URL" --chain-id 56 --broadcast --private-key "$PRIVATE_KEY" --legacy
```
**Strict production example:**
```bash
cd smom-dbis-138
source .env
CW_STRICT_MODE=1 \
CW_GOVERNANCE_ADMIN=0xYourMultisig \
CW_BRIDGE_ADDRESS="$CW_BRIDGE_BSC" forge script script/deploy/DeployCWTokens.s.sol:DeployCWTokens \
--rpc-url "$BSC_RPC_URL" --chain-id 56 --broadcast --private-key "$PRIVATE_KEY" --legacy
```
Or with the wrapper (target one chain only if the script supports `--chain 56`):
```bash
./scripts/deployment/deploy-tokens-and-weth-all-chains-skip-canonical.sh --deploy-cw --chain 56
```
Record the printed addresses for **cWUSDT** and **cWUSDC** per chain.
### D2. Set CWUSDT_<CHAIN> and CWUSDC_<CHAIN> in .env
Append or update in `smom-dbis-138/.env` (replace with actual addresses from D1 output):
```bash
# Example for BSC (56)
CWUSDT_BSC=0x...
CWUSDC_BSC=0x...
# Example for Polygon (137)
CWUSDT_POLYGON=0x...
CWUSDC_POLYGON=0x...
```
Use the chain suffix that matches the deploy script: MAINNET, CRONOS, BSC, POLYGON, GNOSIS, AVALANCHE, BASE, ARBITRUM, OPTIMISM.
### D3. Update token-mapping-multichain.json
**Automated:** After setting `CWUSDT_<CHAIN>` and `CWUSDC_<CHAIN>` in `smom-dbis-138/.env`, run from repo root:
```bash
./scripts/deployment/run-cw-remaining-steps.sh --update-mapping
```
This updates `config/token-mapping-multichain.json` for all chains that have `CWUSDT_*`/`CWUSDC_*` in .env (138→chain pairs; Compliant_USDT_cW, Compliant_USDC_cW, and Compliant_EURC_cW if `CWEURC_*` is set).
**Manual:** For each chain where cW* was deployed, set `addressTo` for the `_cW` entries (replace the `0x0` placeholder) in `config/token-mapping-multichain.json`: Compliant_USDT_cW → CWUSDT_<CHAIN>, Compliant_USDC_cW → CWUSDC_<CHAIN>, Compliant_EURC_cW if cWEURC deployed.
### D3b. Update the active GRU Transport overlay
After the mapping is correct, confirm or update [`config/gru-transport-active.json`](../../config/gru-transport-active.json):
- ensure the destination chain is enabled
- ensure the `transportPairs` entry points at the correct `peerKey`
- ensure `maxOutstanding` policy is set for the pair
- ensure the reserve-verifier reference is correct for hard-peg pairs
- leave public pools inactive until they are actually deployed and recorded in `deployment-status.json`
### D4. Verify on-chain
Confirm the bridge/receiver has MINTER_ROLE and BURNER_ROLE on the cW* token:
```bash
# MINTER_ROLE = keccak256("MINTER_ROLE")
cast keccak "MINTER_ROLE"
# Example: 0x...
# Check cWUSDT on BSC (replace addresses and rpc)
cast call <CWUSDT_BSC> "hasRole(bytes32,address)(bool)" $(cast keccak "MINTER_ROLE") $CW_BRIDGE_BSC --rpc-url $BSC_RPC_URL
cast call <CWUSDT_BSC> "hasRole(bytes32,address)(bool)" $(cast keccak "BURNER_ROLE") $CW_BRIDGE_BSC --rpc-url $BSC_RPC_URL
```
Both should return `true`.
### D5. Configure strict escrow bridge state
For hard-peg deployments using `CWMultiTokenBridgeL1` / `CWMultiTokenBridgeL2`, wire the bridge state after roles are verified.
Example on Chain 138 for `cUSDC -> cWUSDC` on Avalanche:
```bash
cd smom-dbis-138 && source .env
# Allowlist the canonical token on the 138-side escrow bridge
cast send "$CHAIN138_L1_BRIDGE" "configureSupportedCanonicalToken(address,bool)" "$CUSDC_138" true \
--rpc-url "$RPC_URL_138" --private-key "$PRIVATE_KEY" --legacy
# Optional but recommended: cap how much this destination can keep outstanding
cast send "$CHAIN138_L1_BRIDGE" "setMaxOutstanding(address,uint64,uint256)" "$CUSDC_138" 6433500567565415381 1000000000000 \
--rpc-url "$RPC_URL_138" --private-key "$PRIVATE_KEY" --legacy
```
Example on the destination chain:
```bash
cd smom-dbis-138 && source .env
# Freeze the token pair once canonical -> wrapped mapping is correct
cast send "$AVAX_CW_BRIDGE" "freezeTokenPair(address)" "$CUSDC_138" \
--rpc-url "$AVALANCHE_RPC_URL" --private-key "$PRIVATE_KEY" --legacy
# Freeze the Chain 138 peer once the bridge address is confirmed
cast send "$AVAX_CW_BRIDGE" "freezeDestination(uint64)" 138 \
--rpc-url "$AVALANCHE_RPC_URL" --private-key "$PRIVATE_KEY" --legacy
```
Recommended verification calls:
```bash
cast call "$CHAIN138_L1_BRIDGE" "supportedCanonicalToken(address)(bool)" "$CUSDC_138" --rpc-url "$RPC_URL_138"
cast call "$CHAIN138_L1_BRIDGE" "maxOutstanding(address,uint64)(uint256)" "$CUSDC_138" 6433500567565415381 --rpc-url "$RPC_URL_138"
cast call "$AVAX_CW_BRIDGE" "tokenPairFrozen(address)(bool)" "$CUSDC_138" --rpc-url "$AVALANCHE_RPC_URL"
cast call "$AVAX_CW_BRIDGE" "destinationFrozen(uint64)(bool)" 138 --rpc-url "$AVALANCHE_RPC_URL"
```
For the production Avalanche route, `smom-dbis-138/scripts/deployment/complete-nonprefunded-avax-cutover.sh` now applies these controls directly from env:
- `CW_MAX_OUTSTANDING_USDT_AVALANCHE`
- `CW_MAX_OUTSTANDING_USDC_AVALANCHE`
- `CW_FREEZE_AVAX_L2_CONFIG`
### D6. Deploy and attach the canonical reserve verifier
Use the helper script in `smom-dbis-138/script/DeployCWReserveVerifier.s.sol` to deploy the verifier and optionally attach it to `CWMultiTokenBridgeL1`.
Example:
```bash
cd smom-dbis-138 && source .env
CW_L1_BRIDGE="$CHAIN138_L1_BRIDGE" \
CW_STABLECOIN_RESERVE_VAULT="$STABLECOIN_RESERVE_VAULT" \
CW_RESERVE_SYSTEM="$RESERVE_SYSTEM" \
CW_CANONICAL_USDT="$CUSDT_138" \
CW_CANONICAL_USDC="$CUSDC_138" \
CW_USDT_RESERVE_ASSET=0xOfficialUSDTReserveAsset \
CW_USDC_RESERVE_ASSET=0xOfficialUSDCReserveAsset \
forge script script/DeployCWReserveVerifier.s.sol:DeployCWReserveVerifier \
--rpc-url "$RPC_URL_138" --broadcast --private-key "$PRIVATE_KEY" --legacy
```
The script defaults to:
- attaching the verifier to the L1 bridge
- requiring vault backing when `CW_STABLECOIN_RESERVE_VAULT` is set
- requiring reserve-system balance checks when `CW_RESERVE_SYSTEM` is set
- requiring canonical token ownership to match the reserve vault when a vault is set
Recommended post-deploy verification:
```bash
cast call "$CHAIN138_L1_BRIDGE" "reserveVerifier()(address)" --rpc-url "$RPC_URL_138"
cast call <CW_RESERVE_VERIFIER> "verifyLock(address,uint64,uint256)(bool)" "$CUSDC_138" 6433500567565415381 1 --rpc-url "$RPC_URL_138"
cast call <CW_RESERVE_VERIFIER> "getVerificationStatus(address,uint64)((bool,bool,bool,bool,bool,bool,bool,bool,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256))" "$CUSDC_138" 6433500567565415381 --rpc-url "$RPC_URL_138"
```
For the production Avalanche route, `smom-dbis-138/scripts/deployment/complete-nonprefunded-avax-cutover.sh` now also reads and converges:
- `CW_RESERVE_VERIFIER_CHAIN138`
- `CW_STABLECOIN_RESERVE_VAULT`
- `CW_RESERVE_SYSTEM`
- `CW_ATTACH_VERIFIER_TO_L1`
- `CW_REQUIRE_VAULT_BACKING`
- `CW_REQUIRE_RESERVE_SYSTEM_BALANCE`
- `CW_REQUIRE_TOKEN_OWNER_MATCH_VAULT`
- `CW_CANONICAL_USDT`
- `CW_CANONICAL_USDC`
- `CW_USDT_RESERVE_ASSET`
- `CW_USDC_RESERVE_ASSET`
---
## Phase E: Relay and send path (138 → other chains)
### E1. Relay service (138 → Mainnet)
If using the existing CCIPRelayBridge for cW* on Mainnet, that contract is WETH-only and must be **extended** (or a separate cW* receiver deployed and relay logic updated) so that when the relay sends a cUSDT/cUSDC message, the Mainnet receiver mints cWUSDT/cWUSDC. See [RELAY_BRIDGE_ADD_LINK_SUPPORT_RUNBOOK.md](RELAY_BRIDGE_ADD_LINK_SUPPORT_RUNBOOK.md) for the pattern (token whitelist + transfer or mint in `ccipReceive`). Then:
- Configure relay to send c* messages to the cW* receiver on Mainnet.
- Ensure the receiver has MINTER_ROLE on cWUSDT/cWUSDC and holds or mints as designed.
### E2. Direct CCIP (138 → chain)
If Chain 138 uses UniversalCCIPBridge or a dedicated sender to send c* to a destination chain:
- Add destination config for the c* token with **receiver** = the cW* receiver on the destination (e.g. TwoWayTokenBridgeL2 address).
- Ensure the receiver on the destination has MINTER_ROLE on the cW* token and implements `ccipReceive``cW*.mint(recipient, amount)` (see [CW_BRIDGE_APPROACH.md](CW_BRIDGE_APPROACH.md)).
### E3. Test E2E
1. On Chain 138: Lock or transfer cUSDT to the sender/bridge and trigger a send to the target chain (recipient = test address).
2. Wait for CCIP execution on the destination chain.
3. On the destination chain: Verify the recipients cWUSDT balance increased (e.g. `cast call <CWUSDT_<CHAIN>> "balanceOf(address)(uint256)" <recipient> --rpc-url <RPC>`).
---
## New chain checklist (summary)
| Step | Action |
|------|--------|
| 1 | Set `CW_BRIDGE_<CHAIN>` in .env (or use existing from bridge suite). |
| 2 | Run DeployCWTokens for that chain (D1). |
| 3 | Set `CWUSDT_<CHAIN>`, `CWUSDC_<CHAIN>` in .env (D2). |
| 4 | Update `config/token-mapping-multichain.json` `addressTo` for _cW entries (D3). |
| 4a | Confirm `config/gru-transport-active.json` activation and policy refs for the new chain (D3b). |
| 5 | Verify MINTER_ROLE and BURNER_ROLE on cW* for the bridge (D4). |
| 6 | In hard-peg mode, allowlist canonical tokens and set `maxOutstanding` on `CWMultiTokenBridgeL1`, then freeze token pair and destination on `CWMultiTokenBridgeL2` (D5). |
| 7 | Deploy and attach `CWReserveVerifier`, then configure canonical `cUSDT` / `cUSDC` backing requirements (D6). |
| 8 | If cross-chain mint is required, ensure the bridge/receiver code mints cW* in ccipReceive (Phase B or C); then wire relay/direct CCIP (E1, E2) and run E2E test (E3). |
---
## Retry failed cW* deploys (Mainnet, Cronos, Arbitrum)
If the 10 remaining cW* (cWEURC..cWXAUT) failed on **Mainnet** (nonce), **Cronos** (RPC timeout), or **Arbitrum** (gas), use the steps below.
### Mainnet (1) — fix nonce
Deployer nonce on Mainnet must match the RPC (error: `nonce=256 maxNonce=253` means 3 pending txs are ahead of what the RPC has confirmed).
1. **Check nonce and balance:**
`./scripts/deployment/check-deployer-nonce-and-balance.sh`
2. **Options:**
- **Wait** for pending transactions to confirm, then re-run the deploy.
- **Replace/cancel** pending txs: send a 0-value transaction from the deployer to itself with the *next expected nonce* (e.g. 253) and higher gas so it replaces the stuck one; repeat until the queue is cleared.
- Use the same wallet in another tool (e.g. MetaMask) and ensure no other process is sending from this account.
3. **Run deploy** once nonce is aligned:
```bash
cd smom-dbis-138 && source .env
DEPLOY_CWUSDT=0 DEPLOY_CWUSDC=0 CW_BRIDGE_ADDRESS="$CW_BRIDGE_MAINNET" forge script script/deploy/DeployCWTokens.s.sol:DeployCWTokens \
--rpc-url "${ETHEREUM_MAINNET_RPC}" --chain-id 1 --broadcast --private-key "$PRIVATE_KEY" --legacy -vvv
```
Add the printed CWEURC_MAINNET … CWXAUT_MAINNET to `.env`, then run `./scripts/deployment/run-cw-remaining-steps.sh --update-mapping`.
### Cronos (25) — retry when RPC is stable; fix nonce if needed
If you see `invalid nonce; got X, expected Y`, wait for pending txs to confirm or replace/cancel them (same idea as Mainnet). Check: `./scripts/deployment/check-deployer-nonce-and-balance.sh`
```bash
cd smom-dbis-138 && source .env
DEPLOY_CWUSDT=0 DEPLOY_CWUSDC=0 CW_BRIDGE_ADDRESS="$CW_BRIDGE_CRONOS" forge script script/deploy/DeployCWTokens.s.sol:DeployCWTokens \
--rpc-url "${CRONOS_RPC_URL}" --chain-id 25 --broadcast --private-key "$PRIVATE_KEY" --legacy -vvv
```
Then set CWEURC_CRONOS … CWXAUT_CRONOS in `.env` from the script output and run `--update-mapping`.
### Arbitrum (42161) — use current gas; low gas price is enough
[Arbitrum One gas](https://arbiscan.io/gastracker) is often ~0.02 Gwei; the 10-contract deploy then costs &lt;0.001 ETH. If you see "max fee per gas less than block base fee", set `ARBITRUM_GAS_PRICE` slightly above base (e.g. `25000000` = 0.025 gwei). Only use 35 gwei if the network is congested. Run the deploy:
```bash
cd smom-dbis-138 && source .env
# Only if you see "max fee per gas less than block base fee" (check https://arbiscan.io/gastracker)
export ARBITRUM_GAS_PRICE=25000000
./scripts/deployment/deploy-tokens-and-weth-all-chains-skip-canonical.sh --deploy-cw --chain 42161
```
Or single-run without the script (use 25000000 = 0.025 gwei when gas is ~0.02 Gwei):
```bash
DEPLOY_CWUSDT=0 DEPLOY_CWUSDC=0 CW_BRIDGE_ADDRESS="$CW_BRIDGE_ARBITRUM" forge script script/deploy/DeployCWTokens.s.sol:DeployCWTokens \
--rpc-url "${ARBITRUM_MAINNET_RPC}" --chain-id 42161 --broadcast --private-key "$PRIVATE_KEY" --legacy --with-gas-price 25000000 -vvv
```
Then set in `.env` (expected addresses from script when it runs):
- CWEURC_ARBITRUM=0xd6969bC19b53f866C64f2148aE271B2Dae0C58E4
- CWEURT_ARBITRUM=0x3CD9ee18db7ad13616FCC1c83bC6098e03968E66
- CWGBPC_ARBITRUM=0xBeF5A0Bcc0E77740c910f197138cdD90F98d2427
- CWGBPT_ARBITRUM=0x948690147D2e50ffe50C5d38C14125aD6a9FA036
- CWAUDC_ARBITRUM=0x58a8D8F78F1B65c06dAd7542eC46b299629A60dd
- CWJPYC_ARBITRUM=0xFb4B6Cc81211F7d886950158294A44C312abCA29
- CWCHFC_ARBITRUM=0xf9f5D0ACD71C76F9476F10B3F3d3E201F0883C68
- CWCADC_ARBITRUM=0xeE17bB0322383fecCA2784fbE2d4CD7d02b1905B
- CWXAUC_ARBITRUM=0xc9750828124D4c10e7a6f4B655cA8487bD3842EB
- CWXAUT_ARBITRUM=0x328Cd365Bb35524297E68ED28c6fF2C9557d1363
Then run `./scripts/deployment/run-cw-remaining-steps.sh --update-mapping`.
---
## References
- [CW_BRIDGE_TASK_LIST.md](../00-meta/CW_BRIDGE_TASK_LIST.md)
- [CW_BRIDGE_APPROACH.md](CW_BRIDGE_APPROACH.md)
- [CW_TOKENS_AND_NETWORKS.md](../11-references/CW_TOKENS_AND_NETWORKS.md)
- [C_TO_CW_MAPPER_MAPPING.md](../04-configuration/C_TO_CW_MAPPER_MAPPING.md)