- Config, docs, scripts, and backup manifests - Submodule refs unchanged (m = modified content in submodules) Made-with: Cursor
20 KiB
Contract Deployment Runbook
Last Updated: 2026-02-12
Full deployment order: For the canonical sequence (prerequisites → core → PMM/pools → provider → optional → cW* → verification) and remaining recommendations, see DEPLOYMENT_ORDER_OF_OPERATIONS.md.
Deployment safety: Use RPC_URL_138 (Core only, from smom-dbis-138/.env) for all deployments; never use Public RPC. All secrets from smom-dbis-138/.env only. Run a gas/cost estimate before deploy (e.g. cd smom-dbis-138 && ./scripts/deployment/calculate-costs-consolidated.sh). Do not deploy when transactions are stuck — clear tx pool (./scripts/clear-all-transaction-pools.sh), wait ~60s, then retry; use scripts that check nonce when available.
Chain 138 deployment requirements (learned 2026-02-12)
- Gas price: Chain 138 enforces a minimum gas price. Always use
--with-gas-price 1000000000(1 gwei) forforge scriptandforge createwhen deploying to Chain 138; otherwise transactions fail with "Gas price below configured minimum gas price". - On-chain check: After deployments, run
./scripts/verify/check-contracts-on-chain-138.sh(usesRPC_URL_138; optional URL arg). Address list comes fromconfig/smart-contracts-master.jsonwhen available. See CONTRACT_ADDRESSES_REFERENCE, ADDRESS_MATRIX_AND_STATUS. - TransactionMirror: The deploy script can hit a Forge broadcast constructor-args decode error. If so, deploy manually:
forge create contracts/mirror/TransactionMirror.sol:TransactionMirror --constructor-args <ADMIN_ADDRESS> --rpc-url $RPC_URL_138 --private-key $PRIVATE_KEY --gas-price 1000000000.
RPC Routing Summary
Chain 138 uses two standard env vars: RPC_URL_138 (Core, admin/deploy) and RPC_URL_138_PUBLIC (Public, bridge/frontend). See RPC_ENDPOINTS_MASTER.
| Use Case | VMID | IP | Ports | Variable |
|---|---|---|---|---|
| Admin / contract deployment | 2101 | 192.168.11.211 | 8545, 8546 | RPC_URL_138 (Core) |
| Bridge, monitoring, public-facing | 2201 | 192.168.11.221 (FIXED) | 8545, 8546 | RPC_URL_138_PUBLIC (Public) |
Prerequisites
-
.env check (keys only, no secrets printed): From repo root:
./scripts/deployment/preflight-chain138-deploy.sh(RPC, dotenv, nonce). Or from smom-dbis-138:./scripts/deployment/check-env-required.sh— verifiesPRIVATE_KEY,RPC_URL,RPC_URL_138and optional PMM/mainnet/CCIP vars. Usesmom-dbis-138/.envonly for deploy secrets. -
Network access to Chain 138 RPC (set
RPC_URL_138in .env, e.g. http://192.168.11.211:8545 for Core)- Run from a host on the same LAN as Proxmox, or via VPN
- WSL/remote dev environments may get "No route to host" if not on network
-
PRIVATE_KEY in
smom-dbis-138/.env(deployer wallet with gas; same wallet holds LINK for bridge fees) -
Foundry (
forge) installed -
Test all contracts before deploy (Phase 0.8): Run
./scripts/deployment/test-all-contracts-before-deploy.shfrom repo root. This runsforge buildandforge testin smom-dbis-138. Use--dry-runto print commands only;--alltrato include alltra-lifi-settlement;--no-match "Fork|Mainnet|Integration|e2e"for unit tests only. See DEPLOYMENT_ORDER_OF_OPERATIONS § Phase 0.8.
Deprecated bridge (R4)
Do not use CCIPWETH9Bridge at 0x89dd.... Use only the canonical bridge at 0x971c... and set CCIPWETH9_BRIDGE_CHAIN138 in env. See docs/00-meta/RECOMMENDATIONS_OPERATOR_CHECKLIST.md R4.
Env required per deploy script
| Script / phase | Required env | Notes |
|---|---|---|
| DeployMulticall, DeployOracle, DeployMultiSig | PRIVATE_KEY, RPC_URL_138 (Chain 138 Core) |
deploy-all-contracts.sh |
| 01_DeployCore, 02_DeployBridges | PRIVATE_KEY, RPC; 02 needs UNIVERSAL_ASSET_REGISTRY, GOVERNANCE_CONTROLLER |
Phased core |
| DeployCCIPReceiver, DeployCCIPSender | PRIVATE_KEY, CCIP_ROUTER_ADDRESS, ORACLE_AGGREGATOR_ADDRESS; Sender optional: LINK_TOKEN_ADDRESS |
Set in .env; see .env.example |
| DeployWETHBridges (mainnet receiver) | MAINNET_WETH9_BRIDGE_ADDRESS, MAINNET_WETH10_BRIDGE_ADDRESS when configuring cross-chain |
.env.example |
| DeploySmartAccountsKit | PRIVATE_KEY, RPC_URL_138; optional ENTRY_POINT, SMART_ACCOUNT_FACTORY, PAYMASTER if pre-deployed |
Script does not deploy contracts; obtain EntryPoint/Factory from MetaMask kit or ERC-4337 impl and set in env |
| DeployTransactionMirror | PRIVATE_KEY, MIRROR_ADMIN (optional, default deployer) |
If forge script fails with constructor-args decode, use forge create — see § TransactionMirror below |
| DeployReserveSystem | TOKEN_FACTORY in .env |
Phase 6 |
Deploy Core Contracts (Chain 138)
cd smom-dbis-138
source .env
# Verify RPC: curl -s -X POST "$RPC_URL" -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}'
bash scripts/deployment/deploy-all-contracts.sh
Deploys: Multicall, Oracle, MultiSig (WETH9/10 pre-deployed in genesis).
Deploy Unified (Ordered or Parallel)
cd smom-dbis-138
./scripts/deployment/deploy-contracts-unified.sh --mode ordered
# or
./scripts/deployment/deploy-contracts-unified.sh --mode parallel
Deploy WETH Bridges (CCIP)
# From project root (use GAS_PRICE=1000000000 if min-gas-price error)
GAS_PRICE=1000000000 ./scripts/deploy-and-configure-weth9-bridge-chain138.sh
# Then set CCIPWETH9_BRIDGE_CHAIN138 in smom-dbis-138/.env
Smart accounts (ERC-4337)
Script: smom-dbis-138/script/smart-accounts/DeploySmartAccountsKit.s.sol (and DeployAccountWalletRegistryExtended.s.sol for registry).
Required env (in smom-dbis-138/.env): PRIVATE_KEY, RPC_URL_138 (Chain 138 Core). Optional: set ENTRY_POINT, SMART_ACCOUNT_FACTORY, PAYMASTER if already deployed; otherwise the script will deploy and log addresses to set in .env.
Deploy (Chain 138):
cd smom-dbis-138
source .env
forge script script/smart-accounts/DeploySmartAccountsKit.s.sol --rpc-url $RPC_URL_138 --broadcast --with-gas-price 1000000000
# Set ENTRY_POINT, SMART_ACCOUNT_FACTORY, PAYMASTER from output
Verification: Run script; confirm logged addresses match env. See PLACEHOLDERS_AND_TBD Smart Accounts Kit.
TransactionMirror (Chain 138)
Script: script/DeployTransactionMirror.s.sol. Deployed address: Set in smom-dbis-138/.env as TRANSACTION_MIRROR_ADDRESS from the script output (e.g. past deploys: 0xE362aa10D3Af1A16880A799b78D18F923403B55a, 0x4eeF36BBaf706C6da5859cF9B34E9934fEC3E006).
Recommended: Use the combined script; it always checks nonce, validates RPC is active (chainId 138), uses proper gas (1 gwei min), and loads the correct dotenv (smom-dbis-138/.env + config/ip-addresses.conf for RPC fallbacks).
Required in smom-dbis-138/.env: PRIVATE_KEY, RPC_URL_138 (Core RPC, 192.168.11.211:8545). No Public fallback for deployments. Optional: GAS_PRICE or GAS_PRICE_138 (default 1000000000). Before deploying: if Core was read-only, run ./scripts/maintenance/make-rpc-vmids-writable-via-ssh.sh then ./scripts/maintenance/health-check-rpc-2101.sh. See RPC_2101_READONLY_FIX.md.
If you see "Known transaction" or "Replacement transaction underpriced": Clear the tx pool then retry: ./scripts/clear-all-transaction-pools.sh (or RPC-only; see script). Run from a host that can reach RPC_URL_138 (same LAN/VPN):
./scripts/deployment/deploy-transaction-mirror-and-pmm-pool-after-txpool-clear.sh
This deploys TransactionMirror and creates the DODO cUSDT/cUSDC PMM pool, then runs on-chain verification. Core RPC only (no Public fallback). If Core is unreachable, fix read-only and health first (see RPC_2101_READONLY_FIX.md). Options: --dry-run (env, RPC, nonce only); --force (skip RPC check).
Skip stuck nonce manually: Set NEXT_NONCE to the next nonce (e.g. 13370) so the script uses vm.setNonce and deploys at a new address; then set TRANSACTION_MIRROR_ADDRESS in .env to the logged address. The combined script already sets NEXT_NONCE from pending nonce.
Or run the two forge commands manually (ensure RPC is Chain 138 and nonce is correct):
cd smom-dbis-138 && source .env
# Optional: export NEXT_NONCE=<pending nonce> if avoiding a stuck tx
forge script script/DeployTransactionMirror.s.sol:DeployTransactionMirror --rpc-url "$RPC_URL_138" --broadcast --private-key "$PRIVATE_KEY" --with-gas-price 1000000000
forge script script/dex/CreateCUSDTCUSDCPool.s.sol:CreateCUSDTCUSDCPool --rpc-url "$RPC_URL_138" --broadcast --private-key "$PRIVATE_KEY" --with-gas-price 1000000000
If forge script fails with "Failed to decode constructor arguments", deploy via forge create:
cd smom-dbis-138
source .env
ADMIN="${MIRROR_ADMIN:-$(cast wallet address --private-key $PRIVATE_KEY)}"
forge create contracts/mirror/TransactionMirror.sol:TransactionMirror \
--constructor-args "$ADMIN" \
--rpc-url "$RPC_URL_138" \
--private-key "$PRIVATE_KEY" \
--legacy \
--gas-price 1000000000
Or run the helper script (from repo root, from a host on LAN that can reach RPC_URL_138 e.g. 192.168.11.211:8545): ./scripts/deployment/deploy-transaction-mirror-chain138.sh. The script exports ETH_RPC_URL so Forge uses the correct RPC and, on success, updates or appends TRANSACTION_MIRROR_ADDRESS in smom-dbis-138/.env.
AlltraAdapter — setBridgeFee after deploy
After deploying or using AlltraAdapter (138 ↔ ALL Mainnet 651940), set the bridge fee to match ALL Mainnet fee structure.
Required: ALLTRA_ADAPTER_CHAIN138 in smom-dbis-138/.env (adapter address; see config/smart-contracts-master.json). Optional: ALLTRA_BRIDGE_FEE (wei) to pass to setBridgeFee. Deployer must have DEFAULT_ADMIN_ROLE.
cd smom-dbis-138 && source .env
# Use ALLTRA_BRIDGE_FEE from .env if set, else 0.001 ALL (1000000000000000 wei)
FEE="${ALLTRA_BRIDGE_FEE:-1000000000000000}"
cast send "$ALLTRA_ADAPTER_CHAIN138" "setBridgeFee(uint256)" "$FEE" \
--rpc-url "$RPC_URL_138" --private-key "$PRIVATE_KEY" --gas-price 1000000000
Document the final fee in PLACEHOLDERS_AND_TBD § AlltraAdapter when known.
Vault ac* / vdc* / sdc* (Chain 138)
After deploying the Vault System (DeployVaultSystem.s.sol), run the single script that creates all asset/deposit (ac*) and debt (vdc*) tokens via VaultFactory.createVaultWithDecimals.
Required env: VAULT_FACTORY_ADDRESS, PRIVATE_KEY, RPC_URL_138. Optional: OWNER, ENTITY (default deployer); CUSDC_ADDRESS_138, CUSDT_ADDRESS_138 or COMPLIANT_USDC_ADDRESS, COMPLIANT_USDT_ADDRESS (skip currency if unset).
Option A — deploy Vault System then ac/vdc/sdc in one go: run ./scripts/deployment/deploy-vault-system-and-ac-vdc-sdc.sh from smom-dbis-138 (uses PRIVATE_KEY, RPC_URL_138; parses VaultFactory from broadcast and runs ac/vdc/sdc step).
Option B — run steps separately:
cd smom-dbis-138
source .env
forge script script/deploy/vault/DeployVaultSystem.s.sol:DeployVaultSystem \
--rpc-url "$RPC_URL_138" --broadcast --with-gas-price 1000000000
# From output, set VAULT_FACTORY_ADDRESS=0x... then:
forge script script/deploy/vault/DeployAcVdcSdcVaults.s.sol:DeployAcVdcSdcVaults \
--rpc-url "$RPC_URL_138" --broadcast --with-gas-price 1000000000
Deployer must have VAULT_DEPLOYER_ROLE on VaultFactory. Each configured base token gets one vault with deposit token (ac*) and debt token (vdc*), 6 decimals, transferable debt. See MULTI_CHAIN_EXECUTION_DETERMINISTIC_DEPLOYMENT. Full architecture and phased roadmap: see VAULT_SYSTEM_MASTER_TECHNICAL_PLAN.
EnhancedSwapRouter & DODOPMMProvider (post-deploy configuration)
When Uniswap V3, Balancer, or DODO PMM pools exist on Chain 138 / 651940, configure the router and provider so on-chain quotes and swaps work.
EnhancedSwapRouter (set by address with ROUTING_MANAGER_ROLE):
| Config | Method | Env (optional) | When |
|---|---|---|---|
| Uniswap V3 Quoter | setUniswapQuoter(address) |
ENHANCED_SWAP_ROUTER_UNISWAP_QUOTER |
After Uniswap Quoter is deployed on chain |
| Balancer pool (WETH↔stable) | setBalancerPoolId(tokenIn, tokenOut, poolId) |
BALANCER_WETH_USDC_POOL_ID, BALANCER_WETH_USDT_POOL_ID |
After Balancer pool exists |
| Dodoex pool | setDodoPoolAddress(tokenIn, tokenOut, pool) |
Set via cast or script when DODO pool is known | After DODO PMM pool deployed |
Example (Chain 138, after setting env):
cd smom-dbis-138 && source .env
# Uniswap Quoter
[ -n "$ENHANCED_SWAP_ROUTER_UNISWAP_QUOTER" ] && cast send "$ENHANCED_SWAP_ROUTER" "setUniswapQuoter(address)" "$ENHANCED_SWAP_ROUTER_UNISWAP_QUOTER" --rpc-url "$RPC_URL_138" --private-key "$PRIVATE_KEY" --gas-price 1000000000
# Balancer (bytes32 pool IDs; use cast send with 0x-prefixed hex)
# cast send "$ENHANCED_SWAP_ROUTER" "setBalancerPoolId(address,address,bytes32)" <WETH> <USDC> "$BALANCER_WETH_USDC_POOL_ID" ...
DODOPMMProvider: Register existing DODO PMM pools so getQuote / executeSwap work. Address with POOL_MANAGER_ROLE calls registerPool(tokenIn, tokenOut, pool).
# After DODO pool is deployed (e.g. cUSDT↔USDT)
cast send "$DODO_PMM_PROVIDER_ADDRESS" "registerPool(address,address,address)" "<CUSDT>" "<USDT>" "<POOL_ADDRESS>" --rpc-url "$RPC_URL_138" --private-key "$PRIVATE_KEY" --gas-price 1000000000
Optional .env placeholders (see smom-dbis-138/.env.example): ENHANCED_SWAP_ROUTER_UNISWAP_QUOTER, BALANCER_WETH_USDC_POOL_ID, BALANCER_WETH_USDT_POOL_ID, DODO_PMM_PROVIDER_ADDRESS. Until set, router returns 0 for Uniswap/Balancer quotes and DODO provider returns no pool.
Private stabilization pools (Master Plan Phase 2)
XAU-anchored private pools (cUSDT↔XAU, cUSDC↔XAU, cEURT↔XAU) for the Stabilizer; only the Stabilizer or whitelisted keeper should execute swaps. See VAULT_SYSTEM_MASTER_TECHNICAL_PLAN §5.
Script: smom-dbis-138/script/dex/DeployPrivatePoolRegistryAndPools.s.sol — deploys PrivatePoolRegistry and optionally creates pools via DODOPMMIntegration createPool, then registers them in the registry.
Env (in smom-dbis-138/.env):
| Variable | Description |
|---|---|
PRIVATE_KEY |
Deployer (must have POOL_MANAGER_ROLE on DODOPMMIntegration to create pools) |
PRIVATE_POOL_REGISTRY_ADMIN |
Admin for PrivatePoolRegistry (default: deployer) |
DODOPMM_INTEGRATION_ADDRESS |
Deployed DODOPMMIntegration (set to create XAU pools in same run) |
XAU_ADDRESS_138 |
XAU token address on Chain 138 (required to create XAU-anchored pools) |
COMPLIANT_USDT_ADDRESS |
cUSDT on Chain 138 |
COMPLIANT_USDC_ADDRESS |
cUSDC on Chain 138 |
cEURT_ADDRESS_138 |
cEURT on Chain 138 (optional; omit to skip cEURT↔XAU pool) |
Deploy:
cd smom-dbis-138 && source .env
forge script script/dex/DeployPrivatePoolRegistryAndPools.s.sol:DeployPrivatePoolRegistryAndPools \
--rpc-url "$RPC_URL_138" --broadcast --with-gas-price 1000000000
If only the registry is needed (pools created later), leave DODOPMM_INTEGRATION_ADDRESS or XAU_ADDRESS_138 unset. To register existing pools manually: cast send "$PRIVATE_POOL_REGISTRY" "register(address,address,address)" <TOKEN_A> <TOKEN_B> <POOL_ADDRESS> .... Grant STABILIZER_LP_ROLE to allowed LPs when using a wrapper that checks it.
Stabilizer deployment and configuration (Phase 3 + 6)
Deploy the Stabilizer (Master Plan Phase 3) after PrivatePoolRegistry and private XAU-anchored pools exist. The Stabilizer calls checkDeviation() (peg manager or TWAP) and executePrivateSwap(tradeSize, tokenIn, tokenOut) via the private pool registry. Phase 6: TWAP/sustained N-block deviation, per-block volume cap, flash drain recovery target <3 blocks (see OPERATIONS_RUNBOOK Flash Loan Containment).
Deploy (e.g. via forge create or a small script):
- Constructor:
(admin, privatePoolRegistryAddress). - Set in
.env:STABILIZER_ADDRESS,PRIVATE_POOL_REGISTRY_ADDRESS,STABLECOIN_PEG_MANAGER_ADDRESS(or commodity peg), peg asset address.
Configuration (admin):
| Parameter | Method | Typical / notes |
|---|---|---|
| Peg source | setStablecoinPegSource(manager, asset) or setCommodityPegSource(manager, asset) |
One of them; deviation from peg used for checkDeviation() |
| thresholdBps | setThresholdBps(uint256) |
e.g. 50 (0.5%) |
| minBlocksBetweenExecution | setMinBlocksBetweenExecution(uint256) |
e.g. 3–5 (block delay) |
| maxStabilizationVolumePerBlock | setMaxStabilizationVolumePerBlock(uint256) |
Cap per block |
| maxSlippageBps | setMaxSlippageBps(uint256) |
e.g. 100 (1%) |
| maxGasPriceForStabilizer | setMaxGasPriceForStabilizer(uint256) |
MEV resistance; 0 = disabled |
| sustainedDeviationBlocks | setSustainedDeviationBlocks(uint256) |
N blocks over threshold before rebalance (Phase 6) |
Keeper: Grant STABILIZER_KEEPER_ROLE to the keeper EOA or bot:
cast send "$STABILIZER_ADDRESS" "grantRole(bytes32,address)" $(cast keccak "STABILIZER_KEEPER_ROLE()") "$KEEPER_ADDRESS" --rpc-url "$RPC_URL_138" --private-key "$PRIVATE_KEY" --gas-price 1000000000
Operational target (Phase 6): Flash drain recovery <3 blocks. The contract enforces sustained deviation over N blocks, per-block volume cap, and block delay; document in OPERATIONS_RUNBOOK and VAULT_SYSTEM_MASTER_TECHNICAL_PLAN §8/§16.
Contract Verification (Blockscout)
Use the Forge Verification Proxy for forge verify-contract (Blockscout expects module/action in query; Forge sends JSON only). The verification script uses canonical addresses from smom-dbis-138/.env and config/ip-addresses.conf (ORACLE_PROXY, AGGREGATOR_ADDRESS, CCIP_SENDER, CCIPWETH9_BRIDGE_CHAIN138, etc.); run from a host on LAN that can reach Blockscout (192.168.11.140:4000).
Preferred: orchestrated script (starts proxy if needed, timeout 900s default):
source smom-dbis-138/.env 2>/dev/null
./scripts/verify/run-contract-verification-with-proxy.sh
Manual (proxy + verify):
# 1. Start proxy (in separate terminal)
BLOCKSCOUT_URL=http://192.168.11.140:4000 node forge-verification-proxy/server.js
# 2. Run verification
./scripts/verify-contracts-blockscout.sh
See: forge-verification-proxy/README.md, BLOCKSCOUT_FORGE_VERIFICATION_EVALUATION.md. Fallback: manual verification at https://explorer.d-bis.org/address/#verify-contract
Runbooks in sync (R12): BLOCKSCOUT_FIX_RUNBOOK, BLOCKSCOUT_FORGE_VERIFICATION_EVALUATION, this runbook. Full recommendations (R1–R24): RECOMMENDATIONS_OPERATOR_CHECKLIST.
Troubleshooting
| Error | Cause | Fix |
|---|---|---|
No route to host |
Dev machine cannot reach RPC (RPC_URL_138, e.g. 192.168.11.211:8545) | Run from machine on LAN or VPN; or set RPC_URL_138=https://rpc-core.d-bis.org |
PRIVATE_KEY not set |
Missing in .env | Add deployer key to smom-dbis-138/.env |
Gas price below configured minimum gas price |
Chain 138 minimum gas not met | Use --with-gas-price 1000000000 for all forge script / forge create on Chain 138 |
Failed to decode constructor arguments (TransactionMirror) |
Forge broadcast decode bug | Deploy via forge create ... --constructor-args <ADMIN> --gas-price 1000000000 |
pam_chauthtok failed (Blockscout) |
Container PAM restriction | Use Proxmox Web UI: Container 5000 → Options → Password |
pvesm not found (verify-storage) |
Script must run ON Proxmox host | ssh root@r630-01 then run script |