Files
smom-dbis-138/docs/deployment/CUSDT_CUSDC_MULTICHAIN_LIQUIDITY_RUNBOOK.md
2026-03-02 12:14:09 -08:00

12 KiB
Raw Permalink Blame History

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/:

# 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:

# 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).
  • 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):

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 chains 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.

  • Factory: Same address on mainnet, Polygon, BSC, Base, Arbitrum, Optimism: 0x1F98431c8aD98523631AE4a59f267346ea31F984 (Uniswap 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 Uniswaps NonfungiblePositionManager or Uniswap UI.

Helper script (from smom-dbis-138/):

# 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):

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 chains Uniswap V2 (or SushiSwap) factory: createPair(tokenA, tokenB).
  • Add liquidity via router addLiquidity or the pairs mint.

Documentation

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 (or the chains 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. WETHcUSDT, WETHcUSDC):
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.


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 Curves factory/UI.
  • Resources: Curve Docs, Curve Factory.
  • 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
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 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)