246 lines
12 KiB
Markdown
246 lines
12 KiB
Markdown
# 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_<chainId>`, `CUSDC_ADDRESS_<chainId>`. 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_<CHAIN>` (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)" \
|
||
<tokenA> <tokenB> <poolId> \
|
||
--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_<chainId>`, `CUSDC_ADDRESS_<chainId>` 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 <CUSDT_ADDRESS> "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_<CHAIN>` 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=<id>` and `GET /api/v1/report/coingecko?chainId=<id>` should include cUSDT/cUSDC when `CUSDT_ADDRESS_<id>` and `CUSDC_ADDRESS_<id>` 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
|