chore: .gitignore and README updates
Made-with: Cursor
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -29,6 +29,7 @@ datadir/
|
||||
# Foundry
|
||||
out/
|
||||
artifacts/
|
||||
.omnl-poller-state.json
|
||||
cache/
|
||||
broadcast/
|
||||
.cronos-verify/
|
||||
|
||||
@@ -578,6 +578,7 @@ See [ALL Mainnet Master Documentation](../docs/MASTER_INDEX.md) for complete int
|
||||
- [Financial Tokenization (Archived)](docs/archive/status-reports/operations-legacy/FINANCIAL_TOKENIZATION.md) - Historical ISO-20022 and SWIFT FIN tokenization notes
|
||||
- [ALL Mainnet Integration](../docs/MASTER_INDEX.md) - Complete ALL Mainnet (651940) integration guide
|
||||
- [Multi-Chain Deployment](docs/deployment/MULTI_CHAIN_DEPLOYMENT_GUIDE.md) - Multi-chain deployment guide
|
||||
- [HYBX OMNL (reserves, compliance, CCIP mirror)](docs/hybx-omnl/README.md) - Policy, deployment, IPSAS anchor, token-aggregation routes
|
||||
|
||||
### 📊 Operations & Runbooks
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"description": "Desired-state pool spec for Chain 138 DODO PMM. Scripts should create and register only missing pools from this file, not redeploy contracts.",
|
||||
"version": "1.0.0",
|
||||
"updated": "2026-04-15",
|
||||
"version": "1.1.0",
|
||||
"updated": "2026-04-19",
|
||||
"chainId": 138,
|
||||
"defaults": {
|
||||
"lpFeeRate": 3,
|
||||
@@ -11,6 +11,7 @@
|
||||
"enableTwap": false
|
||||
},
|
||||
"tokens": {
|
||||
"cBTC": "0xe94260c555aC1d9D3CC9E1632883452ebDf0082E",
|
||||
"cUSDT": "0x93E66202A11B1772E55407B32B44e5Cd8eda7f22",
|
||||
"cUSDC": "0xf22258f57794CC8E06237084b353Ab30fFfa640b",
|
||||
"cEURC": "0x8085961F9cF02b4d800A3c6d386D31da4B34266a",
|
||||
@@ -55,6 +56,9 @@
|
||||
}
|
||||
},
|
||||
"explicitPairs": [
|
||||
{ "baseSymbol": "cBTC", "quoteSymbol": "cUSDT" },
|
||||
{ "baseSymbol": "cBTC", "quoteSymbol": "cUSDC" },
|
||||
{ "baseSymbol": "cBTC", "quoteSymbol": "cXAUC" },
|
||||
{ "baseSymbol": "cUSDT", "quoteSymbol": "cUSDC" },
|
||||
{ "baseSymbol": "cUSDT", "quoteSymbol": "USDT" },
|
||||
{ "baseSymbol": "cUSDC", "quoteSymbol": "USDC" },
|
||||
@@ -70,6 +74,12 @@
|
||||
{ "baseSymbol": "cXAUT", "quoteSymbol": "cUSDC" },
|
||||
{ "baseSymbol": "cEURC", "quoteSymbol": "cEURT" },
|
||||
{ "baseSymbol": "cGBPC", "quoteSymbol": "cGBPT" },
|
||||
{ "baseSymbol": "cXAUC", "quoteSymbol": "cXAUT" }
|
||||
]
|
||||
{ "baseSymbol": "cXAUC", "quoteSymbol": "cXAUT" },
|
||||
{ "baseSymbol": "WETH", "quoteSymbol": "USDT" },
|
||||
{ "baseSymbol": "WETH", "quoteSymbol": "USDC" },
|
||||
{ "baseSymbol": "cUSDT", "quoteSymbol": "WETH" },
|
||||
{ "baseSymbol": "cUSDC", "quoteSymbol": "WETH" },
|
||||
{ "baseSymbol": "cEURT", "quoteSymbol": "WETH" }
|
||||
],
|
||||
"plannedPairs": []
|
||||
}
|
||||
|
||||
@@ -53,9 +53,9 @@ Per [Contract Deployment and Verification](https://docs.cronos.org/for-dapp-deve
|
||||
- Requires API key in Hardhat config
|
||||
|
||||
### 3. Foundry
|
||||
- `forge verify-contract --verifier blockscout` fails (Cronos Explorer API format differs)
|
||||
- Run `./scripts/deployment/verify-cronos-contracts.sh` to attempt; falls back to manual verification
|
||||
- **Recommended**: Manual verification via the web interface above
|
||||
- `forge verify-contract` does not successfully submit verification to Cronos Explorer (legacy Etherscan-style POST returns 404; Etherscan v2 does not support chain id 25).
|
||||
- Run `./scripts/deployment/verify-cronos-contracts.sh` to confirm bytecode on RPC and print manual steps (optional `CRONOS_EXPORT_SOURCES=1` to regenerate Standard-JSON).
|
||||
- **Recommended**: Manual verification via the web interface above and `docs/deployment/CRONOS_VERIFICATION_RUNBOOK.md`
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -66,6 +66,16 @@ This is the **master index** of all project documentation. Use this as your star
|
||||
- **[36-REGION-BLUEPRINT.md](deployment/36-REGION-BLUEPRINT.md)** - 36-region deployment blueprint
|
||||
- **[DEPLOYMENT_CONFIGURATION_AUDIT.md](deployment/DEPLOYMENT_CONFIGURATION_AUDIT.md)** - Configuration audit
|
||||
|
||||
### HYBX OMNL (reserves, compliance, CCIP mirror)
|
||||
|
||||
- **[DEPLOYMENT_CHECKLIST.md](hybx-omnl/DEPLOYMENT_CHECKLIST.md)** — OMNL deploy and token-aggregation checklist
|
||||
- **[OMNL_RECONCILE_CRON_AND_CI.md](hybx-omnl/OMNL_RECONCILE_CRON_AND_CI.md)** — IPSAS/journal anchor: cron, artifact script, GitHub Actions
|
||||
- **[OPERATIONAL_COMPLIANCE.md](hybx-omnl/OPERATIONAL_COMPLIANCE.md)** — Webhooks, retention, reconciliation anchors
|
||||
- **[OMNL_IPSAS_API.md](hybx-omnl/OMNL_IPSAS_API.md)** — IPSAS / GL API surface
|
||||
- **[CCIP_MIRROR_FLOW.md](hybx-omnl/CCIP_MIRROR_FLOW.md)** — CCIP mirror flow
|
||||
- **[HYBX_OMNL_POLICY_SPEC.md](hybx-omnl/HYBX_OMNL_POLICY_SPEC.md)** — Policy parameters (M0/M1 rules)
|
||||
- **[EXTERNAL_AUDIT_CHECKLIST.md](hybx-omnl/EXTERNAL_AUDIT_CHECKLIST.md)** — Third-party audit scope
|
||||
|
||||
---
|
||||
|
||||
## 🔧 Operations
|
||||
|
||||
@@ -37,7 +37,7 @@ besu.d-bis.org. 1 IN A 70.153.83.83 ; cf_tags=cf-proxied:true
|
||||
blockscout.d-bis.org. 1 IN A 20.215.32.42 ; cf_tags=cf-proxied:true
|
||||
blockscout.d-bis.org. 1 IN A 70.153.83.83 ; cf_tags=cf-proxied:true
|
||||
d-bis.org. 1 IN A 20.215.32.42 ; cf_tags=cf-proxied:true
|
||||
d-bis.org. 1 IN A 20.215.32.15 ; Digital Bank of International Settlements - Defi Oracle Meta Mainnet cf_tags=cf-proxied:true
|
||||
d-bis.org. 1 IN A 20.215.32.15 ; Digital Bank of International Settlements - DeFi Oracle Meta Mainnet cf_tags=cf-proxied:true
|
||||
docs.d-bis.org. 1 IN A 20.8.47.226 ; cf_tags=cf-proxied:false
|
||||
explorer.d-bis.org. 1 IN A 20.215.32.42 ; cf_tags=cf-proxied:true
|
||||
explorer.d-bis.org. 1 IN A 70.153.83.83 ; cf_tags=cf-proxied:true
|
||||
|
||||
@@ -72,10 +72,16 @@ Repeat for each destination chain (BSC, Polygon, Base, Optimism, Avalanche, Cron
|
||||
|
||||
---
|
||||
|
||||
## 4. LINK Funding
|
||||
## 4. LINK Funding and Activation Scope
|
||||
|
||||
- CCIP charges fees for each cross-chain message; bridges typically pay in **LINK** (or native) depending on the contract’s `feeToken` and `ccipSend` usage.
|
||||
- **Per chain**: Ensure the **bridge contract** holds enough **LINK** (and native for gas) so it can pay CCIP fees when users call `sendCrossChain` (or equivalent).
|
||||
- Important distinction:
|
||||
- **Destination-only receipt** on a chain does not by itself require LINK on that chain's bridge contract, because `ccipReceive(...)` does not pay the CCIP fee.
|
||||
- **Outbound sending** from a chain does require a fee payment path on that chain's bridge contract.
|
||||
- Current WEMIX nuance:
|
||||
- `CCIPWETH9Bridge` may be configured for native-fee mode by setting `feeToken = address(0)`.
|
||||
- `CCIPWETH10Bridge` still requires LINK in the current implementation.
|
||||
- **How to fund**:
|
||||
1. Get LINK on the same chain as the bridge (e.g. from an exchange or faucet).
|
||||
2. Transfer LINK to the **bridge contract address** (the same address you use for `addDestination`), or use any approved mechanism (e.g. admin top-up function if the contract has one).
|
||||
|
||||
@@ -84,6 +84,12 @@ This index helps you find the right deployment guide for your needs.
|
||||
- [Cloud for Sovereignty Landing Zone](CLOUD_FOR_SOVEREIGNTY_LANDING_ZONE.md) - Cloud for sovereignty landing zone
|
||||
- [Deployment Firefly Cacti](DEPLOYMENT_FIREFLY_CACTI.md) - Firefly and Cacti deployment
|
||||
|
||||
### HYBX OMNL (reserve commitments, compliance, CCIP mirror, IPSAS GL)
|
||||
- [HYBX OMNL documentation index](../hybx-omnl/README.md) - All OMNL guides in one place
|
||||
- [OMNL deployment checklist](../hybx-omnl/DEPLOYMENT_CHECKLIST.md) - Contracts, env, API, verification
|
||||
- [OMNL reconcile (cron & CI)](../hybx-omnl/OMNL_RECONCILE_CRON_AND_CI.md) - Anchor hash artifacts and automation
|
||||
- [OMNL IPSAS / GL API](../hybx-omnl/OMNL_IPSAS_API.md) - Reporting API surface
|
||||
|
||||
## Historical/Status Reports
|
||||
|
||||
These are historical deployment reports and status documents. Consider archiving if older than 6 months:
|
||||
|
||||
@@ -18,40 +18,50 @@ PRIVATE_KEY=0x0000000000000000000000000000000000000000000000000000000000000000
|
||||
# =============================================================================
|
||||
# RPC Endpoints
|
||||
# =============================================================================
|
||||
# Infura project (used for supported public chains)
|
||||
INFURA_PROJECT_ID=43b945b33d58463a9246cf5ca8aa6286
|
||||
INFURA_PROJECT_SECRET=
|
||||
INFURA_GAS_API=https://gas.api.infura.io/v3/43b945b33d58463a9246cf5ca8aa6286
|
||||
|
||||
# Ethereum Mainnet
|
||||
ETH_MAINNET_RPC_URL=https://eth-mainnet.g.alchemy.com/v2/YOUR_ALCHEMY_KEY
|
||||
# Alternative: ETH_MAINNET_RPC_URL=https://mainnet.infura.io/v3/YOUR_INFURA_KEY
|
||||
ETH_MAINNET_RPC_URL=https://mainnet.infura.io/v3/43b945b33d58463a9246cf5ca8aa6286
|
||||
ETHEREUM_MAINNET_RPC=https://mainnet.infura.io/v3/43b945b33d58463a9246cf5ca8aa6286
|
||||
|
||||
# Cronos (Crypto.com)
|
||||
CRONOS_RPC_URL=https://evm.cronos.org
|
||||
CRONOS_MAINNET_RPC=https://evm.cronos.org
|
||||
|
||||
# BSC (BNB Smart Chain)
|
||||
BSC_RPC_URL=https://bsc-dataseed1.binance.org
|
||||
# Alternative: BSC_RPC_URL=https://bsc-dataseed.binance.org
|
||||
BSC_RPC_URL=https://bsc-mainnet.infura.io/v3/43b945b33d58463a9246cf5ca8aa6286
|
||||
BSC_MAINNET_RPC=https://bsc-mainnet.infura.io/v3/43b945b33d58463a9246cf5ca8aa6286
|
||||
|
||||
# Polygon PoS
|
||||
POLYGON_RPC_URL=https://polygon-rpc.com
|
||||
# Alternative: POLYGON_RPC_URL=https://rpc-mainnet.maticvigil.com
|
||||
POLYGON_RPC_URL=https://polygon-mainnet.infura.io/v3/43b945b33d58463a9246cf5ca8aa6286
|
||||
POLYGON_MAINNET_RPC=https://polygon-mainnet.infura.io/v3/43b945b33d58463a9246cf5ca8aa6286
|
||||
|
||||
# Gnosis Chain (PoA)
|
||||
GNOSIS_RPC_URL=https://rpc.gnosischain.com
|
||||
# Alternative: GNOSIS_RPC_URL=https://xdai-archive.blockscout.com
|
||||
GNOSIS_MAINNET_RPC=https://rpc.gnosischain.com
|
||||
|
||||
# Avalanche C-Chain
|
||||
AVALANCHE_RPC_URL=https://api.avax.network/ext/bc/C/rpc
|
||||
# Alternative: AVALANCHE_RPC_URL=https://avalanche-mainnet.infura.io/v3/YOUR_KEY
|
||||
AVALANCHE_RPC_URL=https://avalanche-mainnet.infura.io/v3/43b945b33d58463a9246cf5ca8aa6286
|
||||
AVALANCHE_MAINNET_RPC=https://avalanche-mainnet.infura.io/v3/43b945b33d58463a9246cf5ca8aa6286
|
||||
|
||||
# Base
|
||||
BASE_RPC_URL=https://mainnet.base.org
|
||||
# Alternative: BASE_RPC_URL=https://base-mainnet.g.alchemy.com/v2/YOUR_KEY
|
||||
BASE_RPC_URL=https://base-mainnet.infura.io/v3/43b945b33d58463a9246cf5ca8aa6286
|
||||
BASE_MAINNET_RPC=https://base-mainnet.infura.io/v3/43b945b33d58463a9246cf5ca8aa6286
|
||||
|
||||
# Arbitrum One
|
||||
ARBITRUM_RPC_URL=https://arb1.arbitrum.io/rpc
|
||||
# Alternative: ARBITRUM_RPC_URL=https://arbitrum-mainnet.infura.io/v3/YOUR_KEY
|
||||
ARBITRUM_RPC_URL=https://arbitrum-mainnet.infura.io/v3/43b945b33d58463a9246cf5ca8aa6286
|
||||
ARBITRUM_MAINNET_RPC=https://arbitrum-mainnet.infura.io/v3/43b945b33d58463a9246cf5ca8aa6286
|
||||
|
||||
# Optimism
|
||||
OPTIMISM_RPC_URL=https://mainnet.optimism.io
|
||||
# Alternative: OPTIMISM_RPC_URL=https://optimism-mainnet.infura.io/v3/YOUR_KEY
|
||||
OPTIMISM_RPC_URL=https://optimism-mainnet.infura.io/v3/43b945b33d58463a9246cf5ca8aa6286
|
||||
OPTIMISM_MAINNET_RPC=https://optimism-mainnet.infura.io/v3/43b945b33d58463a9246cf5ca8aa6286
|
||||
|
||||
# Celo
|
||||
CELO_RPC_URL=https://celo-mainnet.infura.io/v3/43b945b33d58463a9246cf5ca8aa6286
|
||||
CELO_MAINNET_RPC=https://celo-mainnet.infura.io/v3/43b945b33d58463a9246cf5ca8aa6286
|
||||
|
||||
# Chain-138 (DeFi Oracle Meta Mainnet)
|
||||
# For deployment use Core RPC (IP:port); do not use FQDN (DNS/tunnel can fail).
|
||||
@@ -229,6 +239,11 @@ CCIPLOGGER_MAINNET=
|
||||
# =============================================================================
|
||||
# Default: CW_BRIDGE_ADDRESS=0x0; use per-chain overrides (best practice). In .env, CW_BRIDGE_<CHAIN> are set from the deployed bridge suite (CCIP_RELAY_BRIDGE_MAINNET, CCIPWETH9_BRIDGE_*).
|
||||
# Per-chain: CW_BRIDGE_MAINNET, CW_BRIDGE_CRONOS, CW_BRIDGE_BSC, CW_BRIDGE_POLYGON, CW_BRIDGE_GNOSIS, CW_BRIDGE_AVALANCHE, CW_BRIDGE_BASE, CW_BRIDGE_ARBITRUM, CW_BRIDGE_OPTIMISM
|
||||
# Bridge-only operational-role defaults for new cW* deployments:
|
||||
CW_STRICT_MODE=1
|
||||
CW_FREEZE_OPERATIONAL_ROLES=1
|
||||
# Optional: governance multisig/controller that should hold admin and governance metadata roles on newer cW* deployments
|
||||
CW_GOVERNANCE_ADMIN=
|
||||
# After deploy, set:
|
||||
# CWUSDT_CRONOS=0x...
|
||||
# CWUSDC_CRONOS=0x...
|
||||
@@ -338,4 +353,3 @@ WETH10_ADDRESS=${WETH10_MAINNET}
|
||||
```
|
||||
|
||||
3. Never commit `.env` to version control!
|
||||
|
||||
|
||||
21
foundry.toml
21
foundry.toml
@@ -6,6 +6,13 @@
|
||||
# `bash scripts/forge/scope.sh ...` so Forge only sees the contract subtree you
|
||||
# are actively editing.
|
||||
src = "contracts"
|
||||
# Vendored Uniswap V2 trees pin solc 0.5.x / 0.6.x; skip lets `forge test` use 0.8.20
|
||||
# without legacy solc installs. Nothing outside vendor/ imports these paths.
|
||||
skip = [
|
||||
"contracts/vendor/uniswap-v2-core/**/*.sol",
|
||||
"contracts/vendor/uniswap-v2-periphery/**/*.sol",
|
||||
"contracts/vendor/sushiswap-v2/**/*.sol",
|
||||
]
|
||||
out = "out"
|
||||
libs = ["lib"]
|
||||
solc = "0.8.20"
|
||||
@@ -53,6 +60,15 @@ evm_version = "cancun"
|
||||
optimizer = true
|
||||
optimizer_runs = 100
|
||||
via_ir = true
|
||||
# Match default execution target (Chain 138 / Besu uses Cancun-capable clients).
|
||||
evm_version = "cancun"
|
||||
|
||||
[profile.cronos_legacy]
|
||||
optimizer = true
|
||||
optimizer_runs = 100
|
||||
via_ir = true
|
||||
# Cronos chain 25 currently needs pre-Shanghai bytecode; target Paris to avoid PUSH0.
|
||||
evm_version = "paris"
|
||||
|
||||
# RPC endpoints — use: forge create ... --rpc-url chain138
|
||||
# Prevents default localhost:8545 when ETH_RPC_URL not set
|
||||
@@ -66,6 +82,7 @@ cronos = "https://evm.cronos.org"
|
||||
[etherscan]
|
||||
avalanche = { key = "${SNOWTRACE_API_KEY:-${ETHERSCAN_API_KEY}}", url = "https://api.snowtrace.io/api" }
|
||||
arbitrum = { key = "${ARBISCAN_API_KEY:-${ETHERSCAN_API_KEY}}", url = "https://api.arbiscan.io/api" }
|
||||
# Cronos mainnet (chain 25). Etherscan-style API; chainid required for v2.
|
||||
# API key from explorer.cronos.org/register (CRONOSCAN_API_KEY)
|
||||
# Cronos mainnet (chain 25). Legacy entry — forge verify-contract does not
|
||||
# successfully submit to Cronos Explorer (see CRONOS_EXPLORER_OPERATIONS.md).
|
||||
# Kept for cast/ethers compatibility if tooling expects [etherscan].cronos.
|
||||
cronos = { key = "${CRONOSCAN_API_KEY:-${ETHERSCAN_API_KEY}}", url = "https://explorer-api.cronos.org/mainnet/api?chainid=25" }
|
||||
|
||||
@@ -27,6 +27,6 @@ describe('frontend network config', () => {
|
||||
expect(networks.chain2138TestnetEnabled).toBe(true)
|
||||
expect(networks.frontendSourceChainIds).toEqual([138, 2138])
|
||||
expect(networks.defaultFrontendChainId).toBe(2138)
|
||||
expect(networks.defaultFrontendChainName).toBe('Defi Oracle Meta Testnet')
|
||||
expect(networks.defaultFrontendChainName).toBe('DeFi Oracle Meta Testnet')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -30,10 +30,10 @@ const configuredDefaultFrontendChainId = Number(
|
||||
import.meta.env.VITE_DEFAULT_FRONTEND_CHAIN_ID || 138
|
||||
)
|
||||
|
||||
/** Chain 2138 - Defi Oracle Meta Testnet (optional; enable with VITE_ENABLE_CHAIN2138) */
|
||||
/** Chain 2138 - DeFi Oracle Meta Testnet (optional; enable with VITE_ENABLE_CHAIN2138) */
|
||||
export const chain2138Testnet = defineChain({
|
||||
id: 2138,
|
||||
name: 'Defi Oracle Meta Testnet',
|
||||
name: 'DeFi Oracle Meta Testnet',
|
||||
network: 'chain2138-testnet',
|
||||
nativeCurrency: {
|
||||
decimals: 18,
|
||||
@@ -46,7 +46,7 @@ export const chain2138Testnet = defineChain({
|
||||
},
|
||||
blockExplorers: {
|
||||
default: {
|
||||
name: 'Defi Oracle Meta Testnet Explorer',
|
||||
name: 'DeFi Oracle Meta Testnet Explorer',
|
||||
url: explorerUrl2138,
|
||||
},
|
||||
},
|
||||
|
||||
@@ -24,6 +24,11 @@
|
||||
"forge:test:vault": "FORGE_SCOPE=vault bash scripts/forge/scope.sh test --match-path 'test/vault/Ledger.t.sol'",
|
||||
"forge:test:iso": "FORGE_SCOPE=iso4217w bash scripts/forge/scope.sh test --match-path 'test/iso4217w/*.t.sol'",
|
||||
"forge:test:quick": "FORGE_SCOPE=vault bash scripts/forge/scope.sh test --match-contract LedgerTest",
|
||||
"forge:test:omnl": "bash scripts/forge/scope.sh test hybx-omnl",
|
||||
"omnl:verify": "bash scripts/hybx-omnl/verify-deployment.sh",
|
||||
"omnl:reconcile:artifact": "bash scripts/hybx-omnl/omnl-reconcile-artifact.sh",
|
||||
"omnl:validate-cross-chain": "node scripts/hybx-omnl/validate-cross-chain-config.mjs",
|
||||
"omnl:sync-publish": "bash scripts/hybx-omnl/sync-to-publish.sh",
|
||||
"dodo-pools:create": "bash scripts/create-all-dodo-pools-from-token-api.sh",
|
||||
"dodo-pools:dry-run": "DRY_RUN=true bash scripts/create-all-dodo-pools-from-token-api.sh",
|
||||
"prereqs": "bash scripts/deployment/ensure-prerequisites.sh",
|
||||
|
||||
@@ -12,9 +12,9 @@ import {CompliantWrappedToken} from "../../contracts/tokens/CompliantWrappedToke
|
||||
* Env:
|
||||
* PRIVATE_KEY (required)
|
||||
* CW_BRIDGE_ADDRESS (required) — address that can mint/burn (e.g. CCIP receiver or custom bridge)
|
||||
* CW_STRICT_MODE=1 (optional) — revoke deployer MINTER/BURNER after bridge grant
|
||||
* CW_STRICT_MODE=0 (optional override) — by default deployer MINTER/BURNER are revoked after bridge grant
|
||||
* CW_GOVERNANCE_ADMIN=0x... (optional) — grant DEFAULT_ADMIN_ROLE to governance; if strict, revoke deployer admin when governance is set
|
||||
* CW_FREEZE_OPERATIONAL_ROLES=1 (optional) — freeze future MINTER/BURNER changes after setup
|
||||
* CW_FREEZE_OPERATIONAL_ROLES=0 (optional override) — by default freeze future MINTER/BURNER changes after setup
|
||||
* DEPLOY_CWUSDT=1, DEPLOY_CWUSDC=1, DEPLOY_CWUSDW=1, DEPLOY_CWEURC=1, ... (default all 1; set 0 to skip a token)
|
||||
*/
|
||||
contract DeployCWTokens is Script {
|
||||
@@ -24,8 +24,8 @@ contract DeployCWTokens is Script {
|
||||
uint256 pk = vm.envUint("PRIVATE_KEY");
|
||||
address deployer = vm.addr(pk);
|
||||
address bridge = vm.envAddress("CW_BRIDGE_ADDRESS");
|
||||
bool strictMode = vm.envOr("CW_STRICT_MODE", uint256(0)) == 1;
|
||||
bool freezeOperationalRoles = vm.envOr("CW_FREEZE_OPERATIONAL_ROLES", uint256(0)) == 1;
|
||||
bool strictMode = vm.envOr("CW_STRICT_MODE", uint256(1)) == 1;
|
||||
bool freezeOperationalRoles = vm.envOr("CW_FREEZE_OPERATIONAL_ROLES", uint256(1)) == 1;
|
||||
address governanceAdmin = vm.envOr("CW_GOVERNANCE_ADMIN", address(0));
|
||||
require(bridge != address(0), "CW_BRIDGE_ADDRESS required");
|
||||
|
||||
|
||||
@@ -14,6 +14,9 @@ import {CompliantWrappedToken} from "../../contracts/tokens/CompliantWrappedToke
|
||||
* CW_TOKEN_NAME (required)
|
||||
* CW_TOKEN_SYMBOL (required)
|
||||
* CW_TOKEN_DECIMALS (optional, default 6)
|
||||
* CW_STRICT_MODE=0 (optional override) — by default deployer MINTER/BURNER are revoked after bridge grant
|
||||
* CW_GOVERNANCE_ADMIN=0x... (optional) — grant DEFAULT_ADMIN_ROLE to governance; if strict, revoke deployer admin when governance is set
|
||||
* CW_FREEZE_OPERATIONAL_ROLES=0 (optional override) — by default freeze future MINTER/BURNER changes after setup
|
||||
*/
|
||||
contract DeploySingleCWToken is Script {
|
||||
function run() external {
|
||||
@@ -23,6 +26,9 @@ contract DeploySingleCWToken is Script {
|
||||
string memory tokenName = vm.envString("CW_TOKEN_NAME");
|
||||
string memory tokenSymbol = vm.envString("CW_TOKEN_SYMBOL");
|
||||
uint8 decimals_ = uint8(vm.envOr("CW_TOKEN_DECIMALS", uint256(6)));
|
||||
bool strictMode = vm.envOr("CW_STRICT_MODE", uint256(1)) == 1;
|
||||
bool freezeOperationalRoles = vm.envOr("CW_FREEZE_OPERATIONAL_ROLES", uint256(1)) == 1;
|
||||
address governanceAdmin = vm.envOr("CW_GOVERNANCE_ADMIN", address(0));
|
||||
|
||||
require(bridge != address(0), "CW_BRIDGE_ADDRESS required");
|
||||
require(bytes(tokenName).length != 0, "CW_TOKEN_NAME required");
|
||||
@@ -34,10 +40,32 @@ contract DeploySingleCWToken is Script {
|
||||
token.grantRole(token.MINTER_ROLE(), bridge);
|
||||
token.grantRole(token.BURNER_ROLE(), bridge);
|
||||
|
||||
if (strictMode) {
|
||||
token.revokeRole(token.MINTER_ROLE(), admin);
|
||||
token.revokeRole(token.BURNER_ROLE(), admin);
|
||||
}
|
||||
|
||||
if (governanceAdmin != address(0) && governanceAdmin != admin) {
|
||||
token.grantRole(token.DEFAULT_ADMIN_ROLE(), governanceAdmin);
|
||||
}
|
||||
|
||||
if (freezeOperationalRoles) {
|
||||
token.freezeOperationalRoles();
|
||||
}
|
||||
|
||||
if (strictMode && governanceAdmin != address(0) && governanceAdmin != admin) {
|
||||
token.revokeRole(token.DEFAULT_ADMIN_ROLE(), admin);
|
||||
}
|
||||
|
||||
vm.stopBroadcast();
|
||||
|
||||
console.log(tokenSymbol, address(token));
|
||||
console.log(" bridge", bridge);
|
||||
console.log(" strictMode", strictMode);
|
||||
console.log(" governanceAdmin", governanceAdmin);
|
||||
console.log(" operationalRolesFrozen", token.operationalRolesFrozen());
|
||||
console.log(" deployerHasMinter", token.hasRole(token.MINTER_ROLE(), admin));
|
||||
console.log(" deployerHasBurner", token.hasRole(token.BURNER_ROLE(), admin));
|
||||
console.log(" bridgeHasMinter", token.hasRole(token.MINTER_ROLE(), bridge));
|
||||
console.log(" bridgeHasBurner", token.hasRole(token.BURNER_ROLE(), bridge));
|
||||
}
|
||||
|
||||
@@ -7,9 +7,10 @@ import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
||||
|
||||
/**
|
||||
* @title AddLiquidityPMMPoolsChain138
|
||||
* @notice Add liquidity to the three DODO PMM pools on Chain 138 (cUSDT/cUSDC, cUSDT/USDT, cUSDC/USDC).
|
||||
* @notice Add liquidity to the canonical DODO PMM pools on Chain 138, including the stable/WETH lanes.
|
||||
* @dev Env: PRIVATE_KEY, RPC_URL_138, DODO_PMM_INTEGRATION_ADDRESS (or DODO_PMM_INTEGRATION),
|
||||
* POOL_CUSDTCUSDC, POOL_CUSDTUSDT, POOL_CUSDCUSDC,
|
||||
* optional POOL_CUSDTWETH, POOL_CUSDCWETH, POOL_CEURTWETH,
|
||||
* ADD_LIQUIDITY_BASE_AMOUNT, ADD_LIQUIDITY_QUOTE_AMOUNT (e.g. 1000000e6 for 1M units, 6 decimals).
|
||||
* Optional: ADD_LIQUIDITY_CUSDTCUSDC_BASE, ADD_LIQUIDITY_CUSDTCUSDC_QUOTE, etc. for per-pool amounts.
|
||||
* Optional: NEXT_NONCE — set to pending nonce (e.g. after mints) to avoid -32001 "Nonce too low" on broadcast.
|
||||
@@ -31,6 +32,9 @@ contract AddLiquidityPMMPoolsChain138 is Script {
|
||||
address poolCusdtCusdc = vm.envOr("POOL_CUSDTCUSDC", address(0));
|
||||
address poolCusdtUsdt = vm.envOr("POOL_CUSDTUSDT", address(0));
|
||||
address poolCusdcUsdc = vm.envOr("POOL_CUSDCUSDC", address(0));
|
||||
address poolCusdtWeth = vm.envOr("POOL_CUSDTWETH", address(0));
|
||||
address poolCusdcWeth = vm.envOr("POOL_CUSDCWETH", address(0));
|
||||
address poolCeurtWeth = vm.envOr("POOL_CEURTWETH", address(0));
|
||||
|
||||
uint256 defaultBase = vm.envOr("ADD_LIQUIDITY_BASE_AMOUNT", uint256(0));
|
||||
uint256 defaultQuote = vm.envOr("ADD_LIQUIDITY_QUOTE_AMOUNT", uint256(0));
|
||||
@@ -40,6 +44,8 @@ contract AddLiquidityPMMPoolsChain138 is Script {
|
||||
address cusdc = integration.compliantUSDC();
|
||||
address usdt = integration.officialUSDT();
|
||||
address usdc = integration.officialUSDC();
|
||||
address ceurt = vm.envOr("CEURT_ADDRESS_138", address(0xdf4b71c61E5912712C1Bdd451416B9aC26949d72));
|
||||
address weth = vm.envOr("WETH9_ADDRESS_138", vm.envOr("WETH_ADDRESS_138", address(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2)));
|
||||
|
||||
// On Chain 138, cUSDT/USDT and cUSDC/USDC should point at live local mirror quote tokens.
|
||||
// If the configured quote-side addresses have no code on 138, skip those pools to avoid "call to non-contract".
|
||||
@@ -73,6 +79,30 @@ contract AddLiquidityPMMPoolsChain138 is Script {
|
||||
console.log("Added liquidity to cUSDC/USDC pool:", poolCusdcUsdc);
|
||||
}
|
||||
}
|
||||
if (poolCusdtWeth != address(0) && (defaultBase > 0 || defaultQuote > 0)) {
|
||||
uint256 b = vm.envOr("ADD_LIQUIDITY_CUSDTWETH_BASE", defaultBase);
|
||||
uint256 q = vm.envOr("ADD_LIQUIDITY_CUSDTWETH_QUOTE", defaultQuote);
|
||||
if (b > 0 && q > 0) {
|
||||
_approveAndAdd(integration, cusdt, weth, poolCusdtWeth, b, q);
|
||||
console.log("Added liquidity to cUSDT/WETH pool:", poolCusdtWeth);
|
||||
}
|
||||
}
|
||||
if (poolCusdcWeth != address(0) && (defaultBase > 0 || defaultQuote > 0)) {
|
||||
uint256 b = vm.envOr("ADD_LIQUIDITY_CUSDCWETH_BASE", defaultBase);
|
||||
uint256 q = vm.envOr("ADD_LIQUIDITY_CUSDCWETH_QUOTE", defaultQuote);
|
||||
if (b > 0 && q > 0) {
|
||||
_approveAndAdd(integration, cusdc, weth, poolCusdcWeth, b, q);
|
||||
console.log("Added liquidity to cUSDC/WETH pool:", poolCusdcWeth);
|
||||
}
|
||||
}
|
||||
if (poolCeurtWeth != address(0) && (defaultBase > 0 || defaultQuote > 0)) {
|
||||
uint256 b = vm.envOr("ADD_LIQUIDITY_CEURTWETH_BASE", defaultBase);
|
||||
uint256 q = vm.envOr("ADD_LIQUIDITY_CEURTWETH_QUOTE", defaultQuote);
|
||||
if (b > 0 && q > 0) {
|
||||
_approveAndAdd(integration, ceurt, weth, poolCeurtWeth, b, q);
|
||||
console.log("Added liquidity to cEURT/WETH pool:", poolCeurtWeth);
|
||||
}
|
||||
}
|
||||
|
||||
vm.stopBroadcast();
|
||||
}
|
||||
|
||||
@@ -22,9 +22,9 @@ interface IDODOPMMPoolQuote {
|
||||
* FLASH_QUOTE_AMOUNT_RAW gross USDC borrowed / pushed (6 decimals raw)
|
||||
*
|
||||
* Optional:
|
||||
* POOL_CWUSDC_USDC_MAINNET default 0x69776fc607e9edA8042e320e7e43f54d06c68f0E
|
||||
* CWUSDC_MAINNET default canonical cWUSDC
|
||||
* USDC_MAINNET default official USDC
|
||||
* MAX_FLASH_QUOTE_AMOUNT_RAW optional tighter local cap; must not exceed the defended-lane safe cap
|
||||
* MIN_OUT_PMM if unset, derived from querySellQuote(receiver, amount) * MIN_OUT_PMM_NUM / MIN_OUT_PMM_DEN (defaults 985/1000)
|
||||
* MIN_OUT_PMM_NUM / MIN_OUT_PMM_DEN
|
||||
* MIN_OUT_UNWIND if unset, amount + ceil(amount * AAVE_FLASH_PREMIUM_BPS / 10000) + MIN_OUT_UNWIND_BUFFER_RAW
|
||||
@@ -50,6 +50,7 @@ contract RunMainnetAaveCwusdcUsdcQuotePushOnce is Script {
|
||||
address internal constant DEFAULT_POOL = 0x69776fc607e9edA8042e320e7e43f54d06c68f0E;
|
||||
address internal constant DEFAULT_CWUSDC = 0x2de5F116bFcE3d0f922d9C8351e0c5Fc24b9284a;
|
||||
address internal constant DEFAULT_USDC = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48;
|
||||
uint256 internal constant DEFENDED_SAFE_CAP_RAW = 2_964_298;
|
||||
|
||||
function run() external {
|
||||
uint256 pk = vm.envUint("PRIVATE_KEY");
|
||||
@@ -60,6 +61,9 @@ contract RunMainnetAaveCwusdcUsdcQuotePushOnce is Script {
|
||||
address usdc = vm.envOr("USDC_MAINNET", DEFAULT_USDC);
|
||||
address unwinder = vm.envAddress("QUOTE_PUSH_EXTERNAL_UNWINDER_MAINNET");
|
||||
uint256 amount = vm.envUint("FLASH_QUOTE_AMOUNT_RAW");
|
||||
uint256 localCap = vm.envOr("MAX_FLASH_QUOTE_AMOUNT_RAW", DEFENDED_SAFE_CAP_RAW);
|
||||
|
||||
_validateDefendedLane(pool, amount, localCap);
|
||||
|
||||
uint256 minPmmNum = vm.envOr("MIN_OUT_PMM_NUM", uint256(985));
|
||||
uint256 minPmmDen = vm.envOr("MIN_OUT_PMM_DEN", uint256(1000));
|
||||
@@ -150,4 +154,10 @@ contract RunMainnetAaveCwusdcUsdcQuotePushOnce is Script {
|
||||
AaveQuotePushFlashReceiver(receiver).flashQuotePush(usdc, amount, p);
|
||||
vm.stopBroadcast();
|
||||
}
|
||||
|
||||
function _validateDefendedLane(address pool, uint256 amount, uint256 localCap) internal pure {
|
||||
require(pool == DEFAULT_POOL, "defended pool only");
|
||||
require(localCap <= DEFENDED_SAFE_CAP_RAW, "local cap exceeds defended safe cap");
|
||||
require(amount <= localCap, "flash amount exceeds cap");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ contract RunManagedMainnetAaveCwusdcUsdcQuotePushCycle is Script {
|
||||
address internal constant DEFAULT_POOL = 0x69776fc607e9edA8042e320e7e43f54d06c68f0E;
|
||||
address internal constant DEFAULT_CWUSDC = 0x2de5F116bFcE3d0f922d9C8351e0c5Fc24b9284a;
|
||||
address internal constant DEFAULT_USDC = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48;
|
||||
uint256 internal constant DEFENDED_SAFE_CAP_RAW = 2_964_298;
|
||||
|
||||
function run() external {
|
||||
uint256 pk = vm.envUint("PRIVATE_KEY");
|
||||
@@ -42,9 +43,12 @@ contract RunManagedMainnetAaveCwusdcUsdcQuotePushCycle is Script {
|
||||
address usdc = vm.envOr("USDC_MAINNET", DEFAULT_USDC);
|
||||
address unwinder = vm.envAddress("QUOTE_PUSH_EXTERNAL_UNWINDER_MAINNET");
|
||||
uint256 amount = vm.envUint("FLASH_QUOTE_AMOUNT_RAW");
|
||||
uint256 localCap = vm.envOr("MAX_FLASH_QUOTE_AMOUNT_RAW", DEFENDED_SAFE_CAP_RAW);
|
||||
bool harvest = vm.envOr("QUOTE_PUSH_TREASURY_HARVEST", uint256(1)) == 1;
|
||||
uint256 gasHoldbackTargetRaw = vm.envOr("QUOTE_PUSH_TREASURY_GAS_HOLDBACK_TARGET_RAW", uint256(0));
|
||||
|
||||
_validateDefendedLane(pool, amount, localCap);
|
||||
|
||||
QuotePushTreasuryManager manager = QuotePushTreasuryManager(managerAddr);
|
||||
AaveQuotePushFlashReceiver.QuotePushParams memory p =
|
||||
_loadQuotePushParams(receiver, pool, integration, baseToken, unwinder, amount);
|
||||
@@ -70,6 +74,12 @@ contract RunManagedMainnetAaveCwusdcUsdcQuotePushCycle is Script {
|
||||
console.log("receiverSweepableAfter", manager.receiverSweepableQuote());
|
||||
}
|
||||
|
||||
function _validateDefendedLane(address pool, uint256 amount, uint256 localCap) internal pure {
|
||||
require(pool == DEFAULT_POOL, "defended pool only");
|
||||
require(localCap <= DEFENDED_SAFE_CAP_RAW, "local cap exceeds defended safe cap");
|
||||
require(amount <= localCap, "flash amount exceeds cap");
|
||||
}
|
||||
|
||||
function _loadQuotePushParams(
|
||||
address receiver,
|
||||
address pool,
|
||||
|
||||
@@ -95,6 +95,14 @@ contract ImportProviderPoolsToIntegration is Script {
|
||||
_importIfNeeded(json, providerSource, integration, explicitBase, explicitQuote, lpFeeRate, initialPrice, kFactor, enableTwap);
|
||||
}
|
||||
|
||||
for (uint256 i = 0; json.keyExists(string.concat(".plannedPairs[", vm.toString(i), "]")); i++) {
|
||||
string memory baseKey = string.concat(".plannedPairs[", vm.toString(i), "].baseSymbol");
|
||||
string memory quoteKey = string.concat(".plannedPairs[", vm.toString(i), "].quoteSymbol");
|
||||
string memory plannedBase = json.readString(baseKey);
|
||||
string memory plannedQuote = json.readString(quoteKey);
|
||||
_importIfNeeded(json, providerSource, integration, plannedBase, plannedQuote, lpFeeRate, initialPrice, kFactor, enableTwap);
|
||||
}
|
||||
|
||||
vm.stopBroadcast();
|
||||
}
|
||||
|
||||
|
||||
@@ -28,6 +28,14 @@ if [[ ${#CHAIN_FILTER[@]} -eq 0 && -n "${DEPLOY_PMM_L2S_FILTER:-}" ]]; then
|
||||
for n in $DEPLOY_PMM_L2S_FILTER; do n="$(normalize_chain_name "$n")"; [[ -n "$n" ]] && CHAIN_FILTER+=("$n"); done
|
||||
fi
|
||||
|
||||
forge_profile_for_chain() {
|
||||
local chain_id="$1"
|
||||
case "$chain_id" in
|
||||
25) printf '%s' "cronos_legacy" ;;
|
||||
*) printf '%s' "${FOUNDRY_PROFILE:-default}" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
CHAINS=(
|
||||
"BSC:56:BSC_RPC_URL"
|
||||
"POLYGON:137:POLYGON_MAINNET_RPC"
|
||||
@@ -35,18 +43,25 @@ CHAINS=(
|
||||
"OPTIMISM:10:OPTIMISM_MAINNET_RPC"
|
||||
"ARBITRUM:42161:ARBITRUM_MAINNET_RPC"
|
||||
"AVALANCHE:43114:AVALANCHE_RPC_URL"
|
||||
"CRONOS:25:CRONOS_RPC_URL"
|
||||
"CRONOS:25:CRONOS_RPC_URL|CRONOS_RPC"
|
||||
"GNOSIS:100:GNOSIS_MAINNET_RPC"
|
||||
"CELO:42220:CELO_MAINNET_RPC"
|
||||
)
|
||||
|
||||
for entry in "${CHAINS[@]}"; do
|
||||
IFS=: read -r name chain_id rpc_var <<< "$entry"
|
||||
IFS=: read -r name chain_id rpc_vars <<< "$entry"
|
||||
if [[ ${#CHAIN_FILTER[@]} -gt 0 ]] && [[ ! " ${CHAIN_FILTER[*]} " =~ " $name " ]]; then continue; fi
|
||||
|
||||
rpc="${!rpc_var:-}"
|
||||
rpc=""
|
||||
IFS='|' read -r -a rpc_candidates <<< "$rpc_vars"
|
||||
for rpc_var in "${rpc_candidates[@]}"; do
|
||||
if [[ -n "${!rpc_var:-}" ]]; then
|
||||
rpc="${!rpc_var}"
|
||||
break
|
||||
fi
|
||||
done
|
||||
if [[ -z "$rpc" ]]; then
|
||||
echo "Skip $name (chain $chain_id): $rpc_var not set"
|
||||
echo "Skip $name (chain $chain_id): none of ${rpc_vars} set"
|
||||
continue
|
||||
fi
|
||||
|
||||
@@ -85,12 +100,15 @@ for entry in "${CHAINS[@]}"; do
|
||||
echo "WARN $name: using global OFFICIAL_USDC_ADDRESS fallback; set ${usdc_var} or ${usdc_var_alt} for chain-specific safety"
|
||||
fi
|
||||
|
||||
forge_profile="$(forge_profile_for_chain "$chain_id")"
|
||||
echo "=== Deploying DODOPMMIntegration on $name (chain $chain_id) ==="
|
||||
echo "Using Foundry profile: $forge_profile"
|
||||
DODO_VENDING_MACHINE_ADDRESS="$dvm" \
|
||||
OFFICIAL_USDT_ADDRESS="$usdt" \
|
||||
OFFICIAL_USDC_ADDRESS="$usdc" \
|
||||
COMPLIANT_USDT_ADDRESS="$compliant_usdt" \
|
||||
COMPLIANT_USDC_ADDRESS="$compliant_usdc" \
|
||||
FOUNDRY_PROFILE="$forge_profile" \
|
||||
forge script script/dex/DeployDODOPMMIntegration.s.sol:DeployDODOPMMIntegration \
|
||||
--rpc-url "$rpc" \
|
||||
--chain-id "$chain_id" \
|
||||
|
||||
@@ -259,7 +259,7 @@ run_deploy_cw() {
|
||||
fi
|
||||
local gas_opt=""
|
||||
[[ "$chain_id" == "42161" && -n "${ARBITRUM_GAS_PRICE:-}" ]] && gas_opt="--with-gas-price ${ARBITRUM_GAS_PRICE}"
|
||||
run_cmd "${deploy_opts:+$deploy_opts }CW_BRIDGE_ADDRESS=$bridge forge script script/deploy/DeployCWTokens.s.sol:DeployCWTokens --rpc-url \"$rpc\" --chain-id \"$chain_id\" --broadcast --private-key \"\$PRIVATE_KEY\" --legacy ${gas_opt} -vvv" || true
|
||||
run_cmd "${deploy_opts:+$deploy_opts }CW_BRIDGE_ADDRESS=$bridge CW_STRICT_MODE=${CW_STRICT_MODE:-1} CW_FREEZE_OPERATIONAL_ROLES=${CW_FREEZE_OPERATIONAL_ROLES:-1} forge script script/deploy/DeployCWTokens.s.sol:DeployCWTokens --rpc-url \"$rpc\" --chain-id \"$chain_id\" --broadcast --private-key \"\$PRIVATE_KEY\" --legacy ${gas_opt} -vvv" || true
|
||||
echo " → Set CWUSDT_*, CWUSDC_*, CWEURC_*, CWEURT_*, CWGBPC_*, CWGBPT_*, CWAUDC_*, CWJPYC_*, CWCHFC_*, CWCADC_*, CWXAUC_*, CWXAUT_* in .env from script output."
|
||||
}
|
||||
|
||||
|
||||
@@ -32,13 +32,42 @@ run_or_echo() {
|
||||
}
|
||||
|
||||
ensure_rpc() { local rpc="$1"; type ensure_infura_rpc_url &>/dev/null && [[ -n "$rpc" ]] && rpc=$(ensure_infura_rpc_url "$rpc"); echo "$rpc"; }
|
||||
get_token_balance() {
|
||||
local token="$1" owner="$2" rpc="$3"
|
||||
cast call "${token,,}" "balanceOf(address)(uint256)" "${owner,,}" --rpc-url "$rpc" 2>/dev/null || echo "0"
|
||||
}
|
||||
has_sufficient_link() {
|
||||
local token="$1" owner="$2" rpc="$3" needed="$4"
|
||||
local bal
|
||||
bal=$(get_token_balance "$token" "$owner" "$rpc")
|
||||
bal="${bal%% *}"
|
||||
[[ "$bal" =~ ^[0-9]+$ ]] || bal="0"
|
||||
python3 - <<PY
|
||||
bal = int("$bal")
|
||||
needed = int("$needed")
|
||||
raise SystemExit(0 if bal >= needed else 1)
|
||||
PY
|
||||
}
|
||||
print_skip() {
|
||||
local label="$1" token="$2" owner="$3" rpc="$4" needed="$5"
|
||||
local bal
|
||||
bal=$(get_token_balance "$token" "$owner" "$rpc")
|
||||
echo " Skipped (insufficient LINK balance: have ${bal%% *}, need $needed)"
|
||||
}
|
||||
|
||||
if [[ -z "$PRIVATE_KEY" ]]; then
|
||||
echo "ERROR: PRIVATE_KEY not set" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
DEPLOYER_ADDR=$(cast wallet address --private-key "$PRIVATE_KEY" 2>/dev/null || true)
|
||||
if [[ -z "$DEPLOYER_ADDR" ]]; then
|
||||
echo "ERROR: could not derive deployer address from PRIVATE_KEY" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Funding CCIP bridges with LINK (amount per bridge: $LINK_AMOUNT_WEI wei)"
|
||||
echo "Deployer: $DEPLOYER_ADDR"
|
||||
echo ""
|
||||
|
||||
# Chain 138 (Besu: gas estimation often fails; use explicit legacy gas like manual cast)
|
||||
@@ -48,8 +77,20 @@ if [[ -n "${RPC_URL_138:-}" && -n "${LINK_TOKEN_CHAIN138:-${LINK_TOKEN:-}}" ]];
|
||||
rpc=$(ensure_rpc "$RPC_URL_138")
|
||||
echo "Chain 138 (RPC: ${rpc%%\?*}...)"
|
||||
_gas138="--legacy --gas-limit 250000 --gas-price 2000000000"
|
||||
[[ -n "${CCIPWETH9_BRIDGE_CHAIN138:-}" ]] && run_or_echo "cast send $link \"transfer(address,uint256)\" ${CCIPWETH9_BRIDGE_CHAIN138,,} $LINK_AMOUNT_WEI --rpc-url \"$rpc\" --private-key \"\$PRIVATE_KEY\" $_gas138"
|
||||
[[ -n "${CCIPWETH10_BRIDGE_CHAIN138:-}" ]] && run_or_echo "cast send $link \"transfer(address,uint256)\" ${CCIPWETH10_BRIDGE_CHAIN138,,} $LINK_AMOUNT_WEI --rpc-url \"$rpc\" --private-key \"\$PRIVATE_KEY\" $_gas138"
|
||||
if [[ -n "${CCIPWETH9_BRIDGE_CHAIN138:-}" ]]; then
|
||||
if has_sufficient_link "$link" "$DEPLOYER_ADDR" "$rpc" "$LINK_AMOUNT_WEI"; then
|
||||
run_or_echo "cast send $link \"transfer(address,uint256)\" ${CCIPWETH9_BRIDGE_CHAIN138,,} $LINK_AMOUNT_WEI --rpc-url \"$rpc\" --private-key \"\$PRIVATE_KEY\" $_gas138"
|
||||
else
|
||||
print_skip "CHAIN138/WETH9" "$link" "$DEPLOYER_ADDR" "$rpc" "$LINK_AMOUNT_WEI"
|
||||
fi
|
||||
fi
|
||||
if [[ -n "${CCIPWETH10_BRIDGE_CHAIN138:-}" ]]; then
|
||||
if has_sufficient_link "$link" "$DEPLOYER_ADDR" "$rpc" "$LINK_AMOUNT_WEI"; then
|
||||
run_or_echo "cast send $link \"transfer(address,uint256)\" ${CCIPWETH10_BRIDGE_CHAIN138,,} $LINK_AMOUNT_WEI --rpc-url \"$rpc\" --private-key \"\$PRIVATE_KEY\" $_gas138"
|
||||
else
|
||||
print_skip "CHAIN138/WETH10" "$link" "$DEPLOYER_ADDR" "$rpc" "$LINK_AMOUNT_WEI"
|
||||
fi
|
||||
fi
|
||||
echo ""
|
||||
fi
|
||||
|
||||
@@ -58,8 +99,20 @@ if [[ -n "${ETHEREUM_MAINNET_RPC:-}" && -n "${MAINNET_LINK_TOKEN:-${CCIP_ETH_LIN
|
||||
link="${MAINNET_LINK_TOKEN:-$CCIP_ETH_LINK_TOKEN}"
|
||||
rpc=$(ensure_rpc "$ETHEREUM_MAINNET_RPC")
|
||||
echo "Ethereum Mainnet"
|
||||
[[ -n "${MAINNET_CCIP_WETH9_BRIDGE:-}" ]] && run_or_echo "cast send $link \"transfer(address,uint256)\" $MAINNET_CCIP_WETH9_BRIDGE $LINK_AMOUNT_WEI --rpc-url \"$rpc\" --private-key \"\$PRIVATE_KEY\" --legacy"
|
||||
[[ -n "${MAINNET_CCIP_WETH10_BRIDGE:-}" ]] && run_or_echo "cast send $link \"transfer(address,uint256)\" $MAINNET_CCIP_WETH10_BRIDGE $LINK_AMOUNT_WEI --rpc-url \"$rpc\" --private-key \"\$PRIVATE_KEY\" --legacy"
|
||||
if [[ -n "${MAINNET_CCIP_WETH9_BRIDGE:-}" ]]; then
|
||||
if has_sufficient_link "$link" "$DEPLOYER_ADDR" "$rpc" "$LINK_AMOUNT_WEI"; then
|
||||
run_or_echo "cast send $link \"transfer(address,uint256)\" $MAINNET_CCIP_WETH9_BRIDGE $LINK_AMOUNT_WEI --rpc-url \"$rpc\" --private-key \"\$PRIVATE_KEY\" --legacy"
|
||||
else
|
||||
print_skip "ETH/WETH9" "$link" "$DEPLOYER_ADDR" "$rpc" "$LINK_AMOUNT_WEI"
|
||||
fi
|
||||
fi
|
||||
if [[ -n "${MAINNET_CCIP_WETH10_BRIDGE:-}" ]]; then
|
||||
if has_sufficient_link "$link" "$DEPLOYER_ADDR" "$rpc" "$LINK_AMOUNT_WEI"; then
|
||||
run_or_echo "cast send $link \"transfer(address,uint256)\" $MAINNET_CCIP_WETH10_BRIDGE $LINK_AMOUNT_WEI --rpc-url \"$rpc\" --private-key \"\$PRIVATE_KEY\" --legacy"
|
||||
else
|
||||
print_skip "ETH/WETH10" "$link" "$DEPLOYER_ADDR" "$rpc" "$LINK_AMOUNT_WEI"
|
||||
fi
|
||||
fi
|
||||
echo ""
|
||||
fi
|
||||
|
||||
@@ -89,8 +142,20 @@ for label in BSC POLYGON BASE OPTIMISM ARBITRUM AVALANCHE CRONOS GNOSIS CELO WEM
|
||||
[[ -z "$addr9" && -z "$addr10" ]] && continue
|
||||
|
||||
echo "$label"
|
||||
[[ -n "$addr9" ]] && run_or_echo "cast send ${link,,} \"transfer(address,uint256)\" ${addr9,,} $LINK_AMOUNT_WEI --rpc-url \"$rpc\" --private-key \"\$PRIVATE_KEY\" --legacy"
|
||||
[[ -n "$addr10" ]] && run_or_echo "cast send ${link,,} \"transfer(address,uint256)\" ${addr10,,} $LINK_AMOUNT_WEI --rpc-url \"$rpc\" --private-key \"\$PRIVATE_KEY\" --legacy"
|
||||
if [[ -n "$addr9" ]]; then
|
||||
if has_sufficient_link "$link" "$DEPLOYER_ADDR" "$rpc" "$LINK_AMOUNT_WEI"; then
|
||||
run_or_echo "cast send ${link,,} \"transfer(address,uint256)\" ${addr9,,} $LINK_AMOUNT_WEI --rpc-url \"$rpc\" --private-key \"\$PRIVATE_KEY\" --legacy"
|
||||
else
|
||||
print_skip "$label/WETH9" "$link" "$DEPLOYER_ADDR" "$rpc" "$LINK_AMOUNT_WEI"
|
||||
fi
|
||||
fi
|
||||
if [[ -n "$addr10" ]]; then
|
||||
if has_sufficient_link "$link" "$DEPLOYER_ADDR" "$rpc" "$LINK_AMOUNT_WEI"; then
|
||||
run_or_echo "cast send ${link,,} \"transfer(address,uint256)\" ${addr10,,} $LINK_AMOUNT_WEI --rpc-url \"$rpc\" --private-key \"\$PRIVATE_KEY\" --legacy"
|
||||
else
|
||||
print_skip "$label/WETH10" "$link" "$DEPLOYER_ADDR" "$rpc" "$LINK_AMOUNT_WEI"
|
||||
fi
|
||||
fi
|
||||
echo ""
|
||||
done
|
||||
|
||||
|
||||
@@ -125,6 +125,9 @@ fi
|
||||
while IFS=$'\t' read -r base_sym quote_sym; do
|
||||
add_pair "$base_sym" "$quote_sym"
|
||||
done < <(jq -r '.explicitPairs[]? | [.baseSymbol, .quoteSymbol] | @tsv' "$CONFIG_JSON")
|
||||
while IFS=$'\t' read -r base_sym quote_sym; do
|
||||
add_pair "$base_sym" "$quote_sym"
|
||||
done < <(jq -r '.plannedPairs[]? | [.baseSymbol, .quoteSymbol] | @tsv' "$CONFIG_JSON")
|
||||
|
||||
echo "=== Chain 138 PMM Desired-State Inventory ==="
|
||||
echo "Config: $CONFIG_JSON"
|
||||
|
||||
@@ -137,6 +137,9 @@ fi
|
||||
while IFS=$'\t' read -r base_sym quote_sym; do
|
||||
add_pair "$base_sym" "$quote_sym"
|
||||
done < <(jq -r '.explicitPairs[]? | [.baseSymbol, .quoteSymbol] | @tsv' "$CONFIG_JSON")
|
||||
while IFS=$'\t' read -r base_sym quote_sym; do
|
||||
add_pair "$base_sym" "$quote_sym"
|
||||
done < <(jq -r '.plannedPairs[]? | [.baseSymbol, .quoteSymbol] | @tsv' "$CONFIG_JSON")
|
||||
|
||||
zero_addr='0x0000000000000000000000000000000000000000'
|
||||
created=0
|
||||
|
||||
@@ -1,7 +1,18 @@
|
||||
#!/usr/bin/env bash
|
||||
# Verify all four Cronos contracts via Etherscan-style API.
|
||||
# Uses explorer-api.cronos.org (module/action, solidity-standard-json-input).
|
||||
# Run from smom-dbis-138/
|
||||
# Cronos (chain 25): confirm deployments and prepare manual verification.
|
||||
#
|
||||
# Cronos Explorer moved off legacy Etherscan-compatible POST endpoints on the
|
||||
# public /mainnet/api route (404 on contract verification). Etherscan multichain
|
||||
# v2 does not list Cronos chain id 25. Foundry forge verify-contract therefore
|
||||
# cannot submit verification to Cronos reliably from CI.
|
||||
#
|
||||
# This script: (1) checks bytecode on RPC, (2) optionally exports Standard-JSON
|
||||
# inputs for the web UI, (3) prints links to the manual runbook.
|
||||
#
|
||||
# Run from smom-dbis-138/: ./scripts/deployment/verify-cronos-contracts.sh
|
||||
#
|
||||
# Optional: CRONOS_EXPORT_SOURCES=1 — run export-cronos-verification-sources.sh
|
||||
# Optional: CRONOS_REQUIRE_BYTECODE=1 — exit 1 if any listed address has no code (default: 1)
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
@@ -25,66 +36,56 @@ elif [[ -n "${REPO_ROOT:-}" && -f "$REPO_ROOT/.env" ]]; then
|
||||
set +a
|
||||
fi
|
||||
|
||||
RPC="${CRONOS_RPC_URL:-https://evm.cronos.org}"
|
||||
REQUIRE_CODE="${CRONOS_REQUIRE_BYTECODE:-1}"
|
||||
|
||||
if [ -z "${CRONOSCAN_API_KEY:-}" ]; then
|
||||
echo "ERROR: CRONOSCAN_API_KEY not set. Set in .env (from explorer.cronos.org/register)."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
export CRONOSCAN_API_KEY
|
||||
|
||||
echo "Cronos verification (Etherscan-style API)"
|
||||
echo " API: https://explorer-api.cronos.org/mainnet/api"
|
||||
echo " Chain: cronos (from foundry.toml)"
|
||||
echo "Cronos verification (Chain 25)"
|
||||
echo " RPC: $RPC"
|
||||
echo " Explorer: https://explorer.cronos.org"
|
||||
echo " Manual UI: https://explorer.cronos.org/verifyContract"
|
||||
echo ""
|
||||
echo "Note: Automated forge/etherscan verification is not supported against the current"
|
||||
echo " Cronos Explorer API (see docs/04-configuration/CRONOS_EXPLORER_OPERATIONS.md)."
|
||||
echo ""
|
||||
|
||||
verify() {
|
||||
local addr="$1"
|
||||
local contract="$2"
|
||||
local extra_args="${3:-}"
|
||||
echo "Verifying $contract at $addr..."
|
||||
# shellcheck disable=SC2086
|
||||
if forge verify-contract \
|
||||
"$addr" "$contract" \
|
||||
--chain cronos \
|
||||
--etherscan-api-key "$CRONOSCAN_API_KEY" \
|
||||
--skip-is-verified-check \
|
||||
$extra_args \
|
||||
--watch 2>&1; then
|
||||
echo " ✓ $contract verified"
|
||||
else
|
||||
echo " ✗ $contract verification failed"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
CONTRACTS=(
|
||||
"0x99B3511A2d315A497C8112C1fdd8D508d4B1E506:WETH9"
|
||||
"0x3304b747E565a97ec8AC220b0B6A1f6ffDB837e6:WETH10"
|
||||
"0x3Cc23d086fCcbAe1e5f3FE2bA4A263E1D27d8Cab:CCIPWETH9Bridge"
|
||||
"0x105F8A15b819948a89153505762444Ee9f324684:CCIPWETH10Bridge"
|
||||
)
|
||||
|
||||
FAIL=0
|
||||
|
||||
# WETH9 - no constructor args
|
||||
verify "0x99B3511A2d315A497C8112C1fdd8D508d4B1E506" "contracts/tokens/WETH.sol:WETH" || FAIL=$((FAIL+1))
|
||||
|
||||
# WETH10 - no constructor args
|
||||
verify "0x3304b747E565a97ec8AC220b0B6A1f6ffDB837e6" "contracts/tokens/WETH10.sol:WETH10" || FAIL=$((FAIL+1))
|
||||
|
||||
# CCIPWETH9Bridge - constructor(router, weth9, linkToken)
|
||||
verify "0x3Cc23d086fCcbAe1e5f3FE2bA4A263E1D27d8Cab" "contracts/ccip/CCIPWETH9Bridge.sol:CCIPWETH9Bridge" \
|
||||
"--constructor-args $(cast abi-encode 'constructor(address,address,address)' 0xE26B0A098D861d5C7d9434aD471c0572Ca6EAa67 0x99B3511A2d315A497C8112C1fdd8D508d4B1E506 0x8c80A01F461f297Df7F9DA3A4f740D7297C8Ac85)" || FAIL=$((FAIL+1))
|
||||
|
||||
# CCIPWETH10Bridge - constructor(router, weth10, linkToken)
|
||||
verify "0x105F8A15b819948a89153505762444Ee9f324684" "contracts/ccip/CCIPWETH10Bridge.sol:CCIPWETH10Bridge" \
|
||||
"--constructor-args $(cast abi-encode 'constructor(address,address,address)' 0xE26B0A098D861d5C7d9434aD471c0572Ca6EAa67 0x3304b747E565a97ec8AC220b0B6A1f6ffDB837e6 0x8c80A01F461f297Df7F9DA3A4f740D7297C8Ac85)" || FAIL=$((FAIL+1))
|
||||
|
||||
echo "On-chain bytecode:"
|
||||
for entry in "${CONTRACTS[@]}"; do
|
||||
addr="${entry%%:*}"
|
||||
name="${entry##*:}"
|
||||
code=$(cast code "$addr" --rpc-url "$RPC" 2>/dev/null || echo "0x")
|
||||
if [[ "${#code}" -gt 10 ]]; then
|
||||
echo " ✓ $name $addr"
|
||||
else
|
||||
echo " ✗ $name $addr — no bytecode"
|
||||
FAIL=$((FAIL + 1))
|
||||
fi
|
||||
done
|
||||
echo ""
|
||||
if [ "$FAIL" -eq 0 ]; then
|
||||
echo "All four Cronos contracts verified."
|
||||
exit 0
|
||||
else
|
||||
echo "$FAIL contract(s) failed."
|
||||
echo ""
|
||||
echo "Manual verification (recommended):"
|
||||
echo " 1. ./scripts/deployment/export-cronos-verification-sources.sh"
|
||||
echo " 2. Open https://explorer.cronos.org/verifyContract"
|
||||
echo " 3. Follow docs/deployment/CRONOS_VERIFICATION_RUNBOOK.md"
|
||||
|
||||
if [[ "${CRONOS_EXPORT_SOURCES:-0}" == "1" ]] && [[ -x "$SCRIPT_DIR/export-cronos-verification-sources.sh" ]]; then
|
||||
echo "Regenerating Standard-JSON inputs (.cronos-verify/)..."
|
||||
bash "$SCRIPT_DIR/export-cronos-verification-sources.sh" || true
|
||||
echo ""
|
||||
fi
|
||||
|
||||
echo "Next steps (manual verification required):"
|
||||
echo " 1. docs/deployment/CRONOS_VERIFICATION_RUNBOOK.md"
|
||||
echo " 2. Or: ./scripts/deployment/export-cronos-verification-sources.sh"
|
||||
echo " then upload each *_standard_input.json at https://explorer.cronos.org/verifyContract"
|
||||
echo ""
|
||||
|
||||
if [[ "$REQUIRE_CODE" == "1" ]] && [[ "$FAIL" -gt 0 ]]; then
|
||||
echo "ERROR: $FAIL contract(s) missing bytecode on $RPC" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "verify-cronos-contracts.sh finished (deployment OK; verification is manual on Cronos)."
|
||||
exit 0
|
||||
|
||||
@@ -16,6 +16,8 @@ declare -A ROOT_SCRIPT_SCOPE_ALIASES=(
|
||||
["DeployCCIPWETH9Bridge.s.sol"]="ccip"
|
||||
["DeployCCIPWETH10Bridge.s.sol"]="ccip"
|
||||
["DeployComplianceRegistry.s.sol"]="compliance"
|
||||
["DeployOMNLStack.s.sol"]="hybx-omnl"
|
||||
["DeployMirrorCoordinator.s.sol"]="hybx-omnl"
|
||||
["DeployCompliantUSDC.s.sol"]="tokens"
|
||||
["DeployCompliantUSDT.s.sol"]="tokens"
|
||||
["DeployCWAssetReserveVerifier.s.sol"]="bridge/integration"
|
||||
@@ -55,6 +57,8 @@ Examples:
|
||||
FORGE_SCOPE=vault bash scripts/forge/scope.sh test --match-path 'test/vault/*.t.sol'
|
||||
|
||||
Notes:
|
||||
- Root `forge test` / `forge build` (no scope) honor `foundry.toml` `[profile.default] skip` for
|
||||
legacy Uniswap V2 vendor trees (old solc); scoped builds unchanged.
|
||||
- Omit [scope] to use FORGE_SCOPE, otherwise default to 'full'.
|
||||
- 'full' preserves the historical repo-wide Forge behavior.
|
||||
- Any other scope is resolved relative to contracts/, for example:
|
||||
|
||||
@@ -95,11 +95,14 @@ if [[ "$RUN_ADD_LIQUIDITY" == true ]]; then
|
||||
export POOL_CUSDTCUSDC="${POOL_CUSDTCUSDC:-0x9e89bAe009adf128782E19e8341996c596ac40dC}"
|
||||
export POOL_CUSDTUSDT="${POOL_CUSDTUSDT:-0x866Cb44b59303d8dc5f4F9E3E7A8e8b0bf238d66}"
|
||||
export POOL_CUSDCUSDC="${POOL_CUSDCUSDC:-0xc39B7D0F40838cbFb54649d327f49a6DAC964062}"
|
||||
export POOL_CUSDTWETH="${POOL_CUSDTWETH:-0xaE38a008Ba4Dbf8D9F141D03e9dC8f7Dbe0ce17c}"
|
||||
export POOL_CUSDCWETH="${POOL_CUSDCWETH:-0xAAE68830a55767722618E869882c6Ed064Cc1eb2}"
|
||||
export POOL_CEURTWETH="${POOL_CEURTWETH:-0x4a64c886cedF00db42ea5B946D6b304121ad9529}"
|
||||
if [[ -n "${DODO_PMM_INTEGRATION:-}" || -n "${DODO_PMM_INTEGRATION_ADDRESS:-}" ]]; then
|
||||
# Use pending nonce so broadcast does not get -32001 "Nonce too low" (mints just used N and N+1)
|
||||
NEXT_NONCE=$(cast nonce "$DEPLOYER" --rpc-url "$RPC" --block pending 2>/dev/null || true)
|
||||
[[ -n "$NEXT_NONCE" && "$NEXT_NONCE" =~ ^[0-9]+$ ]] && export NEXT_NONCE || unset -v NEXT_NONCE
|
||||
echo "Running AddLiquidityPMMPoolsChain138 (cUSDT/cUSDC pool only if base/quote set)..."
|
||||
echo "Running AddLiquidityPMMPoolsChain138..."
|
||||
forge script script/dex/AddLiquidityPMMPoolsChain138.s.sol:AddLiquidityPMMPoolsChain138 \
|
||||
--rpc-url "$RPC" --broadcast --private-key "$PRIVATE_KEY" --with-gas-price 1000000000 --gas-estimate-multiplier 150
|
||||
echo "Add-liquidity done."
|
||||
@@ -109,7 +112,8 @@ if [[ "$RUN_ADD_LIQUIDITY" == true ]]; then
|
||||
fi
|
||||
else
|
||||
echo "To add liquidity next: set ADD_LIQUIDITY_BASE_AMOUNT and ADD_LIQUIDITY_QUOTE_AMOUNT (base units, 6 decimals),"
|
||||
echo "POOL_CUSDTCUSDC (and optional POOL_CUSDTUSDT, POOL_CUSDCUSDC), DODO_PMM_INTEGRATION in .env, then run:"
|
||||
echo "POOL_CUSDTCUSDC (and optional POOL_CUSDTUSDT, POOL_CUSDCUSDC, POOL_CUSDTWETH, POOL_CUSDCWETH, POOL_CEURTWETH),"
|
||||
echo "DODO_PMM_INTEGRATION in .env, then run:"
|
||||
echo " forge script script/dex/AddLiquidityPMMPoolsChain138.s.sol:AddLiquidityPMMPoolsChain138 --rpc-url \$RPC_URL_138 --broadcast --private-key \$PRIVATE_KEY"
|
||||
echo "Or run this script with --add-liquidity to mint and add in one go (uses half of minted for cUSDT/cUSDC pool)."
|
||||
fi
|
||||
|
||||
@@ -17,7 +17,9 @@ DEST_RELAY_BRIDGE_ALLOWLIST=0x2bF74583206A49Be07E0E8A94197C12987AbD7B5
|
||||
RELAYER_PRIVATE_KEY=${PRIVATE_KEY}
|
||||
RELAYER_ADDRESS=0x4A666F96fC8764181194447A7dFdb7d471b301C8
|
||||
|
||||
START_BLOCK=2706088
|
||||
# Historical cW backfill is complete; cold starts should resume near head rather
|
||||
# than requeueing legacy unsupported canonical-token messages.
|
||||
START_BLOCK=latest
|
||||
POLL_INTERVAL=5000
|
||||
CONFIRMATION_BLOCKS=1
|
||||
MAX_RETRIES=3
|
||||
|
||||
@@ -804,6 +804,33 @@ export class RelayService {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Dedicated cW bridge workers can replay historical messages for canonical
|
||||
// assets that are no longer configured on the destination multi-token bridge.
|
||||
// When that mapping is absent, the destination bridge will always revert with
|
||||
// "token not configured", so retire the message instead of retry-looping it.
|
||||
if (targetBridgeContract.canonicalToMirrored && data && tokenAmounts.length === 0) {
|
||||
try {
|
||||
const decodedPayload = ethers.AbiCoder.defaultAbiCoder().decode(
|
||||
['address', 'address', 'uint256'],
|
||||
data
|
||||
);
|
||||
const canonicalToken = ethers.getAddress(decodedPayload[0]);
|
||||
const mirroredToken = await targetBridgeContract.canonicalToMirrored(canonicalToken);
|
||||
if (mirroredToken === ethers.ZeroAddress) {
|
||||
this.logger.warn(
|
||||
`Skipping queued message ${messageId}; canonical token ${canonicalToken} is not configured on destination bridge ${targetBridge}`
|
||||
);
|
||||
await this.messageQueue.markProcessed(messageId);
|
||||
return null;
|
||||
}
|
||||
} catch (mappingProbeErr) {
|
||||
this.logger.debug(
|
||||
`canonicalToMirrored probe skipped for ${messageId}; continuing with relay attempt`,
|
||||
mappingProbeErr
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Map token addresses from source chain to destination chain
|
||||
const mappedTokenAmounts = tokenAmounts.map(ta => {
|
||||
const sourceToken = ethers.getAddress(ta.token);
|
||||
|
||||
@@ -17,7 +17,8 @@ export const RelayBridgeABI = [
|
||||
"function ccipReceive(tuple(bytes32 messageId, uint64 sourceChainSelector, bytes sender, bytes data, tuple(address token, uint256 amount, uint8 amountType)[] tokenAmounts) calldata message) external",
|
||||
"event CrossChainTransferCompleted(bytes32 indexed messageId, uint64 indexed sourceChainSelector, address indexed recipient, uint256 amount)",
|
||||
"function processed(bytes32) view returns (bool)",
|
||||
"function processedTransfers(bytes32) view returns (bool)"
|
||||
"function processedTransfers(bytes32) view returns (bool)",
|
||||
"function canonicalToMirrored(address canonicalToken) view returns (address)"
|
||||
];
|
||||
|
||||
export const ERC20ABI = [
|
||||
|
||||
@@ -10,10 +10,12 @@
|
||||
"dev": "ts-node src/index.ts",
|
||||
"test": "jest",
|
||||
"test:ci": "jest --runInBand",
|
||||
"test:omnl": "jest --runInBand --testPathPattern=omnl",
|
||||
"lint": "eslint src --ext .ts",
|
||||
"generate:route-matrix:v2": "ts-node scripts/generate-route-matrix-v2.ts",
|
||||
"migrate": "node -r dotenv/config dist/database/migrations.js",
|
||||
"example:partner-payloads": "node scripts/resolve-partner-payloads-example.mjs"
|
||||
"example:partner-payloads": "node scripts/resolve-partner-payloads-example.mjs",
|
||||
"omnl:reconcile": "node scripts/omnl-reconcile-report.mjs"
|
||||
},
|
||||
"dependencies": {
|
||||
"axios": "^1.13.5",
|
||||
|
||||
@@ -18,3 +18,15 @@ export const strictRateLimiter = rateLimit({
|
||||
standardHeaders: true,
|
||||
legacyHeaders: false,
|
||||
});
|
||||
|
||||
/** Stricter limit for RPC-heavy /api/v1/omnl/* (stacks with apiRateLimiter). */
|
||||
const omnlWindowMs = parseInt(process.env.OMNL_RATE_LIMIT_WINDOW_MS || '60000', 10);
|
||||
const omnlMax = parseInt(process.env.OMNL_RATE_LIMIT_MAX || '30', 10);
|
||||
|
||||
export const omnlRateLimiter = rateLimit({
|
||||
windowMs: omnlWindowMs,
|
||||
max: omnlMax,
|
||||
message: 'Too many OMNL API requests from this IP, please try again later.',
|
||||
standardHeaders: true,
|
||||
legacyHeaders: false,
|
||||
});
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import express, { Express, Request, Response, NextFunction } from 'express';
|
||||
import path from 'path';
|
||||
import { readFileSync, existsSync } from 'fs';
|
||||
import cors from 'cors';
|
||||
import compression from 'compression';
|
||||
import { apiRateLimiter, strictRateLimiter } from './middleware/rate-limit';
|
||||
@@ -15,7 +17,10 @@ import arbitrageRoutes from './routes/arbitrage';
|
||||
import aggregatorRouteMatrixRoutes from './routes/aggregator-routes';
|
||||
import partnerPayloadRoutes from './routes/partner-payloads';
|
||||
import plannerV2Routes from './routes/planner-v2';
|
||||
import omnlRoutes from './routes/omnl';
|
||||
import omnlIpsasRoutes from './routes/omnl-ipsas';
|
||||
import { MultiChainIndexer } from '../indexer/chain-indexer';
|
||||
import { OmnlEventPoller } from '../indexer/omnl-event-poller';
|
||||
import { getDatabasePool } from '../database/client';
|
||||
import winston from 'winston';
|
||||
|
||||
@@ -42,6 +47,7 @@ export class ApiServer {
|
||||
private port: number;
|
||||
private indexerEnabled: boolean;
|
||||
private indexer: MultiChainIndexer | null;
|
||||
private omnlPoller: OmnlEventPoller | null;
|
||||
|
||||
private resolveTrustProxySetting(): boolean | number | string {
|
||||
const raw = (process.env.EXPRESS_TRUST_PROXY ?? process.env.TRUST_PROXY ?? '1').trim();
|
||||
@@ -58,6 +64,7 @@ export class ApiServer {
|
||||
this.port = parseInt(process.env.PORT || '3000', 10);
|
||||
this.indexerEnabled = this.resolveFeatureFlag('ENABLE_INDEXER', true);
|
||||
this.indexer = this.indexerEnabled ? new MultiChainIndexer() : null;
|
||||
this.omnlPoller = this.resolveFeatureFlag('ENABLE_OMNL_EVENT_POLLER', false) ? new OmnlEventPoller() : null;
|
||||
|
||||
this.setupMiddleware();
|
||||
this.setupRoutes();
|
||||
@@ -126,6 +133,24 @@ export class ApiServer {
|
||||
}
|
||||
});
|
||||
|
||||
const dashboardPath = path.join(__dirname, '../../public/omnl-dashboard.html');
|
||||
this.app.get('/omnl/dashboard', (req: Request, res: Response) => {
|
||||
const tok = process.env.OMNL_DASHBOARD_TOKEN?.trim();
|
||||
if (tok) {
|
||||
const q = String(req.query.access_token ?? '').trim();
|
||||
const h = String(req.headers['x-omnl-dashboard-token'] ?? '').trim();
|
||||
if (q !== tok && h !== tok) {
|
||||
res.status(401).type('text/plain').send('Unauthorized: set access_token query or X-OMNL-Dashboard-Token header');
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (!existsSync(dashboardPath)) {
|
||||
res.status(404).type('text/plain').send('omnl-dashboard.html missing');
|
||||
return;
|
||||
}
|
||||
res.type('html').send(readFileSync(dashboardPath, 'utf8'));
|
||||
});
|
||||
|
||||
// API routes
|
||||
this.app.use('/api/v1', tokenRoutes);
|
||||
this.app.use('/api/v1', configRoutes);
|
||||
@@ -138,6 +163,8 @@ export class ApiServer {
|
||||
this.app.use('/api/v1', arbitrageRoutes);
|
||||
this.app.use('/api/v1', aggregatorRouteMatrixRoutes);
|
||||
this.app.use('/api/v1', partnerPayloadRoutes);
|
||||
this.app.use('/api/v1', omnlRoutes);
|
||||
this.app.use('/api/v1', omnlIpsasRoutes);
|
||||
this.app.use('/api/v2', plannerV2Routes);
|
||||
|
||||
// Admin routes (stricter rate limit)
|
||||
@@ -152,6 +179,9 @@ export class ApiServer {
|
||||
health: '/health',
|
||||
api: '/api/v1',
|
||||
apiV2: '/api/v2',
|
||||
omnlOpenApi: '/api/v1/omnl/openapi.json',
|
||||
omnlCatalog: '/api/v1/omnl/catalog',
|
||||
omnlDashboard: '/omnl/dashboard',
|
||||
},
|
||||
});
|
||||
});
|
||||
@@ -183,6 +213,8 @@ export class ApiServer {
|
||||
logger.info('Token aggregation indexer disabled by ENABLE_INDEXER flag');
|
||||
}
|
||||
|
||||
this.omnlPoller?.start();
|
||||
|
||||
// Start server
|
||||
this.app.listen(this.port, () => {
|
||||
logger.info(`Token Aggregation Service listening on port ${this.port}`);
|
||||
@@ -196,6 +228,7 @@ export class ApiServer {
|
||||
}
|
||||
|
||||
async stop(): Promise<void> {
|
||||
this.omnlPoller?.stop();
|
||||
this.indexer?.stopAll();
|
||||
logger.info('Server stopped');
|
||||
}
|
||||
|
||||
@@ -115,7 +115,7 @@ const FALLBACK_ADDRESSES: Record<string, Partial<Record<number, string>>> = {
|
||||
[42161]: '0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9', // Arbitrum USDT
|
||||
[8453]: '0xfde4C96c8593536E31F229EA8f37b2ADa2699bb2', // Base USDT
|
||||
[43114]: '0x9702230A8Ea53601f5cD2dc00fDBc13d4dF4A8c7', // Avalanche USDT
|
||||
[CHAIN_25]: '0x66e4286603D22FF153A6547700f37C7Eae42F8E2', // Cronos USDT
|
||||
[CHAIN_25]: '0x66e428c3f67a68878562e79A0234c1F83c208770', // Cronos USDT
|
||||
[42220]: '0x48065fbBE25f71C9282ddf5e1cD6D6A887483D5e', // Celo USDT
|
||||
[1111]: '0xA649325Aa7C5093d12D6F98EB4378deAe68CE23F', // Wemix USDT
|
||||
},
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Chain ID 2138 — Defi Oracle Meta Testnet
|
||||
# Chain ID 2138 — DeFi Oracle Meta Testnet
|
||||
# Copy to .env.chain2138 (or merge into smom-dbis-138/.env) and fill in values.
|
||||
# Runbook: proxmox parent repo docs/testnet/DEFI_ORACLE_META_TESTNET_2138_RUNBOOK.md
|
||||
# Chainlist metadata: pr-workspace/chains/_data/chains/eip155-2138.json
|
||||
|
||||
@@ -3,12 +3,12 @@ pragma solidity ^0.8.20;
|
||||
|
||||
/**
|
||||
* @title Chain2138TestnetConfig
|
||||
* @notice Configuration constants for Chain ID 2138 (Defi Oracle Meta Testnet) tests.
|
||||
* @notice Configuration constants for Chain ID 2138 (DeFi Oracle Meta Testnet) tests.
|
||||
* @dev Mirror of Chain138Config pattern; override RPC via env in off-chain runners.
|
||||
*/
|
||||
library Chain2138TestnetConfig {
|
||||
uint256 public constant CHAIN_ID = 2138;
|
||||
string public constant NETWORK_NAME = "Defi Oracle Meta Testnet";
|
||||
string public constant NETWORK_NAME = "DeFi Oracle Meta Testnet";
|
||||
/// @dev Default public RPC from pr-workspace/chains/_data/chains/eip155-2138.json; use LAN/staging in CI as needed.
|
||||
string public constant RPC_URL = "https://rpc.public-2138.defi-oracle.io";
|
||||
string public constant EXPLORER_URL = "https://public-2138.defi-oracle.io";
|
||||
|
||||
@@ -6,8 +6,65 @@ import "../../contracts/registry/UniversalAssetRegistry.sol";
|
||||
import "../../contracts/bridge/UniversalCCIPBridge.sol";
|
||||
import "../../contracts/bridge/BridgeOrchestrator.sol";
|
||||
import "../../contracts/governance/GovernanceController.sol";
|
||||
import "../../contracts/interfaces/IRegulatedAssetMetadata.sol";
|
||||
import "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";
|
||||
|
||||
/// @dev Minimal `IRegulatedAssetMetadata` so `_syncAssetMetadata` ABI-decodes cleanly
|
||||
/// (Solidity try/catch does not catch return-data decode panics for empty/wrong-length returndata).
|
||||
contract MockRegulatedAsset is IRegulatedAssetMetadata {
|
||||
function assetId() external pure override returns (bytes32) {
|
||||
return keccak256("MOCK_ASSET");
|
||||
}
|
||||
|
||||
function assetVersionId() external pure override returns (bytes32) {
|
||||
return keccak256("MOCK_VER");
|
||||
}
|
||||
|
||||
function governanceProfileId() external pure override returns (bytes32) {
|
||||
return bytes32(0);
|
||||
}
|
||||
|
||||
function supervisionProfileId() external pure override returns (bytes32) {
|
||||
return bytes32(0);
|
||||
}
|
||||
|
||||
function storageNamespace() external pure override returns (bytes32) {
|
||||
return bytes32(0);
|
||||
}
|
||||
|
||||
function primaryJurisdiction() external pure override returns (string memory) {
|
||||
return "";
|
||||
}
|
||||
|
||||
function regulatoryDisclosureURI() external pure override returns (string memory) {
|
||||
return "";
|
||||
}
|
||||
|
||||
function reportingURI() external pure override returns (string memory) {
|
||||
return "";
|
||||
}
|
||||
|
||||
function canonicalUnderlyingAsset() external pure override returns (address) {
|
||||
return address(0);
|
||||
}
|
||||
|
||||
function supervisionRequired() external pure override returns (bool) {
|
||||
return false;
|
||||
}
|
||||
|
||||
function governmentApprovalRequired() external pure override returns (bool) {
|
||||
return false;
|
||||
}
|
||||
|
||||
function minimumUpgradeNoticePeriod() external pure override returns (uint256) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
function wrappedTransport() external pure override returns (bool) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @title UniversalBridge Integration Tests
|
||||
* @notice Comprehensive tests for all asset types through full bridge flow.
|
||||
@@ -86,10 +143,9 @@ contract UniversalBridgeTest is Test {
|
||||
// Add validator
|
||||
registry.addValidator(user1);
|
||||
|
||||
// Create proposal
|
||||
address mockToken = makeAddr("mockToken2");
|
||||
vm.etch(mockToken, hex"00");
|
||||
|
||||
// Create proposal (real contract: metadata sync ABI-decodes return data; empty etch breaks execute)
|
||||
address mockToken = address(new MockRegulatedAsset());
|
||||
|
||||
bytes32 proposalId = registry.proposeAsset(
|
||||
mockToken,
|
||||
UniversalAssetRegistry.AssetType.Stablecoin,
|
||||
|
||||
Reference in New Issue
Block a user