# cUSDT / cUSDC Multi-Chain Deployment and Liquidity Runbook **Purpose:** Deploy cUSDT and cUSDC to other blockchain networks, create Dodo PMM and Uniswap liquidity pools, and add cUSDT/cUSDC to Balancer, Curve, and other protocols. **Status:** Active runbook **Last updated:** 2026-02-20 --- ## Overview | Phase | Description | |-------|-------------| | **1. Deploy cUSDT/cUSDC to other chains** | Deploy CompliantUSDT and CompliantUSDC on each target chain (BSC, Polygon, Base, etc.) using existing Forge scripts. | | **2. Dodo PMM** | Create PMM pools: Chain 138 (existing) + L2s via `deploy-pmm-all-l2s.sh`; optionally use cUSDT/cUSDC on L2s once deployed. | | **3. Uniswap** | Create Uniswap V2/V3 pools for cUSDT/cUSDC on each chain (Uniswap factory + add liquidity). | | **4. Balancer** | Create Balancer pools for cUSDT/cUSDC (Balancer UI or factory); on Ethereum mainnet, register pool IDs in EnhancedSwapRouter. | | **5. Curve** | Add cUSDT/cUSDC to Curve pools (Curve UI or factory; mainnet-focused). | | **6. Other protocols** | Document 1inch (aggregator), and other DEXes as needed. | --- ## Prerequisites - **.env** in `smom-dbis-138/` with: - `PRIVATE_KEY` — deployer (must hold native gas token on each target chain). - Per-chain RPC: `BSC_RPC_URL`, `POLYGON_MAINNET_RPC`, `BASE_MAINNET_RPC`, `ARBITRUM_MAINNET_RPC`, `OPTIMISM_MAINNET_RPC`, `AVALANCHE_RPC_URL`, `CRONOS_RPC_URL`, `GNOSIS_MAINNET_RPC`, `ETHEREUM_MAINNET_RPC`. - **Gas:** Deployer funded with ETH (mainnet), BNB (BSC), MATIC (Polygon), etc. - **Compliance:** Same compliance/owner setup as Chain 138 if required (e.g. `COMPLIANCE_ADMIN`, `USDT_OWNER`, `USDC_OWNER`). --- ## Phase 1: Deploy cUSDT and cUSDC to Other Chains cUSDT/cUSDC are deployed per chain using the same contracts; only the RPC and chain ID change. ### Chains to target - **Ethereum** (1) — mainnet - **BSC** (56) - **Polygon** (137) - **Base** (8453) - **Arbitrum** (42161) - **Optimism** (10) - **Avalanche** (43114) - **Cronos** (25) - **Gnosis** (100) ### Deploy commands (per chain) From `smom-dbis-138/`: ```bash # Set RPC and chain for the target network export RPC_URL="$POLYGON_MAINNET_RPC" # or BSC_RPC_URL, BASE_MAINNET_RPC, etc. export CHAIN_ID=137 # 56=BSC, 137=Polygon, 8453=Base, etc. # Deploy CompliantUSDT forge script script/DeployCompliantUSDT.s.sol:DeployCompliantUSDT \ --rpc-url "$RPC_URL" \ --chain-id "$CHAIN_ID" \ --broadcast \ --private-key "$PRIVATE_KEY" \ -vv # Deploy CompliantUSDC forge script script/DeployCompliantUSDC.s.sol:DeployCompliantUSDC \ --rpc-url "$RPC_URL" \ --chain-id "$CHAIN_ID" \ --broadcast \ --private-key "$PRIVATE_KEY" \ -vv ``` Record the deployed addresses and set them in `.env` (see **Env vars** below). ### Optional: batch deploy script Use `scripts/deployment/deploy-cusdt-cusdc-all-chains.sh` (see below) to loop over chains and deploy both tokens, then record addresses. ### Env vars (per-chain cUSDT/cUSDC) After deployment, set in `.env` for token-aggregation and PMM: ```bash # Example for Polygon (137) CUSDT_ADDRESS_137=0x... CUSDC_ADDRESS_137=0x... # Example for BSC (56) CUSDT_ADDRESS_56=0x... CUSDC_ADDRESS_56=0x... ``` Use the same pattern for other chain IDs: `CUSDT_ADDRESS_`, `CUSDC_ADDRESS_`. Token-aggregation reads these when provided (see `canonical-tokens.ts` and `.env.example`). --- ## Phase 2: Dodo PMM Liquidity Pools ### Chain 138 (existing) - **DODOPMMIntegration** and mock DVM are already deployed. - Create cUSDT/cUSDC pools: `createCUSDTUSDTPool`, `createCUSDCUSDCPool`, `createCUSDTCUSDCPool` via `scripts/setup-dodo-pools.sh` or cast (see [DODO_PMM_INTEGRATION.md](../integration/DODO_PMM_INTEGRATION.md)). - Add liquidity: `DODOPMMIntegration.addLiquidity(pool, baseAmount, quoteAmount)`. ### L2s (BSC, Polygon, Base, etc.) **Option A — Official USDT/USDC on L2 (current script behavior)** Pools are USDT/USDC on each L2 (no cUSDT/cUSDC on L2 yet): ```bash cd smom-dbis-138 # Set in .env: BSC_RPC_URL, BSC_DODO_VENDING_MACHINE_ADDRESS, BSC_OFFICIAL_USDT_ADDRESS, BSC_OFFICIAL_USDC_ADDRESS # (and same for POLYGON_*, BASE_*, etc.). DODO DVM addresses: https://docs.dodoex.io/developer/contracts/dodo-v1-v2/contracts-address ./scripts/deployment/deploy-pmm-all-l2s.sh # Or one chain: ./scripts/deployment/deploy-pmm-all-l2s.sh --chain polygon ``` **Option B — cUSDT/cUSDC on L2** After deploying cUSDT/cUSDC to an L2 (Phase 1), set that chain’s compliant addresses and DVM, then run the same script with overrides so the integration uses cUSDT/cUSDC for that chain (e.g. `POLYGON_COMPLIANT_USDT_ADDRESS`, `POLYGON_COMPLIANT_USDC_ADDRESS`). The script currently uses `COMPLIANT_USDT_ADDRESS` / `COMPLIANT_USDC_ADDRESS` as fallback; for L2 cUSDT/cUSDC you would set per-chain env (e.g. `POLYGON_CUSDT_ADDRESS_137`) and, if the script supports it, pass them as compliant tokens for that chain. If not yet supported, deploy DODOPMMIntegration for that L2 manually with `COMPLIANT_USDT_ADDRESS` / `COMPLIANT_USDC_ADDRESS` set to the L2 cUSDT/cUSDC addresses. Record `DODOPMM_INTEGRATION_` (or per-chain integration address) in `.env`. --- ## Phase 3: Uniswap Liquidity Pools Uniswap does not have a single “create pool” script in this repo. Create pools on each chain as follows. ### Uniswap V3 (recommended for mainnet and L2s) - **Factory:** Same address on mainnet, Polygon, BSC, Base, Arbitrum, Optimism: `0x1F98431c8aD98523631AE4a59f267346ea31F984` ([Uniswap V3 Deployments](https://docs.uniswap.org/contracts/v3/deployments)). - **Create pool:** Call `UniswapV3Factory.createPool(tokenA, tokenB, fee)` (e.g. fee 500 = 0.05% for stables). - **Initialize:** Call `UniswapV3Pool.initialize(sqrtPriceX96)`. - **Add liquidity:** Use Uniswap’s NonfungiblePositionManager or Uniswap UI. **Helper script (from `smom-dbis-138/`):** ```bash # Create cUSDT/cUSDC pool on Polygon (set CUSDT_ADDRESS_137, CUSDC_ADDRESS_137 and POLYGON_MAINNET_RPC in .env) RPC_URL=$POLYGON_MAINNET_RPC ./scripts/deployment/create-uniswap-v3-pool-cusdt-cusdc.sh # Or pass tokens and RPC explicitly TOKEN_A=0x... TOKEN_B=0x... FEE=500 RPC_URL=$ETHEREUM_MAINNET_RPC ./scripts/deployment/create-uniswap-v3-pool-cusdt-cusdc.sh ``` Example (manual cast, Ethereum mainnet): ```bash FACTORY=0x1F98431c8aD98523631AE4a59f267346ea31F984 cast send $FACTORY "createPool(address,address,uint24)" $CUSDT_ADDRESS_1 $CUSDC_ADDRESS_1 500 \ --rpc-url "$ETHEREUM_MAINNET_RPC" --private-key "$PRIVATE_KEY" # Then initialize and add liquidity via Uniswap UI or position manager. ``` ### Uniswap V2 - Use the chain’s Uniswap V2 (or SushiSwap) factory: `createPair(tokenA, tokenB)`. - Add liquidity via router `addLiquidity` or the pair’s `mint`. ### Documentation - Uniswap V3: https://docs.uniswap.org/contracts/v3/reference/core/UniswapV3Factory - Uniswap V2: https://docs.uniswap.org/contracts/v2/reference/smart-contracts/factory After creating pools, the token-aggregation service can index them if it is configured to index Uniswap V2/V3 on that chain (see pool-indexer and config). --- ## Phase 4: Balancer - **Create pools:** Use [Balancer](https://app.balancer.fi/) (or the chain’s Balancer app) to create a pool containing cUSDT and cUSDC (and optionally WETH or other assets). Balancer uses pool IDs (bytes32). - **Ethereum mainnet + EnhancedSwapRouter:** After creating a pool, set the pool ID in EnhancedSwapRouter for the pairs you route (e.g. WETH–cUSDT, WETH–cUSDC): ```bash cast send $ENHANCED_SWAP_ROUTER \ "setBalancerPoolId(address,address,bytes32)" \ \ --rpc-url "$ETHEREUM_MAINNET_RPC" --private-key "$PRIVATE_KEY" ``` See [ENHANCED_SWAP_ROUTER_UPGRADE.md](../bridge/trustless/ENHANCED_SWAP_ROUTER_UPGRADE.md). --- ## Phase 5: Curve - **Curve** is used mainly on Ethereum (and a few L2s). Add cUSDT/cUSDC to an existing Curve pool or create a new pool via Curve’s factory/UI. - **Resources:** [Curve Docs](https://docs.curve.fi/), [Curve Factory](https://curve.fi/). - **EnhancedSwapRouter:** Uses Curve for large swaps; ensure the router is configured with the correct Curve pool (or pool getter) for cUSDT/cUSDC if you add a dedicated pool. --- ## Phase 6: Other Protocols - **1inch:** Aggregator only; it discovers existing pools. Once cUSDT/cUSDC have liquidity on Uniswap, Balancer, Curve, DODO, etc., 1inch will quote them if the pool is indexed. - **Other DEXes:** Per chain (e.g. SushiSwap, Camelot, etc.): create pairs/pools via their factory or UI, then add liquidity. Token-aggregation can index additional DEXes if the pool indexer is extended (see `services/token-aggregation/src/indexer/pool-indexer.ts`). --- ## Checklist Summary | Step | Action | Script / reference | |------|--------|--------------------| | 1a | Deploy cUSDT to each target chain | `forge script script/DeployCompliantUSDT.s.sol` with chain RPC | | 1b | Deploy cUSDC to each target chain | `forge script script/DeployCompliantUSDC.s.sol` with chain RPC | | 1c | Set `CUSDT_ADDRESS_`, `CUSDC_ADDRESS_` in .env | — | | 2a | Dodo PMM on Chain 138 | `scripts/setup-dodo-pools.sh`, [DODO_PMM_INTEGRATION.md](../integration/DODO_PMM_INTEGRATION.md) | | 2b | Dodo PMM on L2s | `./scripts/deployment/deploy-pmm-all-l2s.sh` | | 3 | Uniswap V2/V3 pools for cUSDT/cUSDC | `scripts/deployment/create-uniswap-v3-pool-cusdt-cusdc.sh` or Uniswap factory + init + add liquidity (per chain) | | 4 | Balancer pools + EnhancedSwapRouter pool IDs | Balancer UI/factory; `setBalancerPoolId` on router | | 5 | Curve pools | Curve UI/factory | | 6 | Token-aggregation / CoinGecko | Set canonical addresses; report API and CoinGecko submission | --- ## Verification - **Token on chain:** `cast call "totalSupply()(uint256)" --rpc-url $RPC_URL` (and same for cUSDC). Expect 6 decimals (e.g. 1e6 = 1 token). - **PMM integration (L2):** After `deploy-pmm-all-l2s.sh`, set `DODOPMM_INTEGRATION_` in .env; verify with `cast call $INTEGRATION "..." --rpc-url $RPC`. - **Uniswap pool:** `cast call $UNISWAP_V3_FACTORY "getPool(address,address,uint24)(address)" $TOKEN_A $TOKEN_B 500 --rpc-url $RPC_URL`; non-zero address = pool exists. - **Token-aggregation:** `GET /api/v1/report/token-list?chainId=` and `GET /api/v1/report/coingecko?chainId=` should include cUSDT/cUSDC when `CUSDT_ADDRESS_` and `CUSDC_ADDRESS_` are set in the service env. --- ## Troubleshooting | Issue | Check | Fix | |-------|--------|-----| | Deploy fails "insufficient funds" | Deployer balance on that chain | Fund with native gas (ETH, BNB, MATIC, etc.) | | Deploy fails "nonce" / "replacement" | Pending or stuck tx on that chain | Clear mempool or use next nonce; see [RPC_NODES_BLOCK_PRODUCTION_FIX](../../../docs/09-troubleshooting/RPC_NODES_BLOCK_PRODUCTION_FIX.md) for Chain 138 | | deploy-pmm-all-l2s skips chain | Missing RPC or DVM/token env for that chain | Set `BSC_RPC_URL`, `BSC_DODO_VENDING_MACHINE_ADDRESS`, `BSC_OFFICIAL_USDT_ADDRESS`, `BSC_OFFICIAL_USDC_ADDRESS` (and equivalent for other chains) | | Uniswap createPool reverts | Token order or fee; pool may already exist | Use factory `getPool(tokenA, tokenB, fee)` first; 0x0 = create, else pool exists | | Token-aggregation report missing token | Canonical list only has 138/651940 by default; L2 needs env | Set `CUSDT_ADDRESS_56`, `CUSDC_ADDRESS_56`, etc. in env used by token-aggregation (see `canonical-tokens.ts`) | --- ## Related docs - [ALL_MAINNETS_DEPLOYMENT_RUNBOOK.md](ALL_MAINNETS_DEPLOYMENT_RUNBOOK.md) — CCIP, Trustless, Oracle, PMM overview - [DVM_DEPLOYMENT_CHECK.md](DVM_DEPLOYMENT_CHECK.md) — DODO DVM on Chain 138 vs L2s - [DODO_PMM_INTEGRATION.md](../integration/DODO_PMM_INTEGRATION.md) — Pool creation and swap methods - [ENHANCED_SWAP_ROUTER_UPGRADE.md](../bridge/trustless/ENHANCED_SWAP_ROUTER_UPGRADE.md) — Balancer/Curve/Uniswap on mainnet - [OPERATOR_NEXT_STEPS_RUNBOOK.md](OPERATOR_NEXT_STEPS_RUNBOOK.md) — G1 PMM on L2s, G2/G3 Trustless