7 Commits

Author SHA1 Message Date
defiQUG
fcd55aa9c4 feat(token-aggregation): add historical pricing context, backfill, and indexer hardening
Some checks failed
CI/CD Pipeline / Solidity Contracts (pull_request) Failing after 1m6s
CI/CD Pipeline / Security Scanning (pull_request) Successful in 12m42s
CI/CD Pipeline / Lint and Format (pull_request) Failing after 42s
CI/CD Pipeline / Terraform Validation (pull_request) Failing after 25s
CI/CD Pipeline / Kubernetes Validation (pull_request) Successful in 27s
HYBX OMNL TypeScript & anchor / token-aggregation build + reconcile artifact (pull_request) Failing after 49s
OMNL reconcile anchor / Run omnl:reconcile and upload artifacts (pull_request) Failing after 26s
Validation / validate-genesis (pull_request) Successful in 36s
Validation / validate-terraform (pull_request) Failing after 28s
Validation / validate-kubernetes (pull_request) Failing after 12s
Validation / validate-smart-contracts (pull_request) Failing after 13s
Validation / validate-security (pull_request) Failing after 1m39s
Validation / validate-documentation (pull_request) Failing after 18s
2026-04-25 23:45:07 -07:00
defiQUG
c3b1b2cebc config: add explicit chain138 sentry and alltra hybx classes 2026-04-25 08:14:07 -07:00
defiQUG
4540ec4480 feat: add universal resource policy profile registry 2026-04-24 22:06:26 -07:00
defiQUG
045a6a9245 docs: mark besu base templates as placeholder advertised hosts 2026-04-24 22:02:47 -07:00
defiQUG
f3d2961b97 feat: add hybx omnl stack and gas pmm tooling
Some checks failed
CI/CD Pipeline / Lint and Format (push) Failing after 46s
CI/CD Pipeline / Terraform Validation (push) Failing after 35s
CI/CD Pipeline / Kubernetes Validation (push) Successful in 37s
Deploy ChainID 138 / Deploy ChainID 138 (push) Failing after 1m50s
HYBX OMNL TypeScript & anchor / token-aggregation build + reconcile artifact (push) Failing after 2m19s
Validation / validate-genesis (push) Successful in 51s
Validation / validate-terraform (push) Failing after 39s
Validation / validate-kubernetes (push) Failing after 10s
CI/CD Pipeline / Solidity Contracts (push) Failing after 12m56s
Validation / validate-smart-contracts (push) Failing after 12s
CI/CD Pipeline / Security Scanning (push) Failing after 15m52s
Validation / validate-security (push) Failing after 10m59s
Validation / validate-documentation (push) Failing after 17s
Validate Token List / validate (push) Failing after 30s
OMNL reconcile anchor / Run omnl:reconcile and upload artifacts (push) Failing after 26s
Verify Deployment / Verify Deployment (push) Failing after 56s
2026-04-24 12:56:40 -07:00
defiQUG
c3d4c786fa Standardize deployment env and deployer handling 2026-04-22 14:47:28 -07:00
defiQUG
768168de5e chore: .gitignore and README updates
Made-with: Cursor
2026-04-21 22:00:55 -07:00
191 changed files with 10303 additions and 1835 deletions

71
.github/workflows/hybx-omnl-ts.yml vendored Normal file
View File

@@ -0,0 +1,71 @@
# TypeScript build + IPSAS/journal anchor (no Forge — keeps PR feedback fast).
name: HYBX OMNL TypeScript & anchor
on:
workflow_dispatch:
pull_request:
branches: [main, develop]
paths:
- 'contracts/hybx-omnl/**'
- 'services/token-aggregation/**'
- 'config/omnl-*.json'
- 'config/hybx-omnl-*.json'
- 'config/deployment-omnl.example.env'
- 'scripts/hybx-omnl/**'
- 'test/hybx-omnl/**'
- '.github/workflows/hybx-omnl-ts.yml'
- '.github/workflows/omnl-reconcile.yml'
push:
branches: [main, develop]
paths:
- 'contracts/hybx-omnl/**'
- 'services/token-aggregation/**'
- 'config/omnl-*.json'
- 'config/hybx-omnl-*.json'
- 'config/deployment-omnl.example.env'
- 'scripts/hybx-omnl/**'
- 'test/hybx-omnl/**'
- '.github/workflows/hybx-omnl-ts.yml'
- '.github/workflows/omnl-reconcile.yml'
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
ts-and-anchor:
name: token-aggregation build + reconcile artifact
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Validate cross-chain OMNL config
run: node scripts/hybx-omnl/validate-cross-chain-config.mjs
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: npm
cache-dependency-path: services/token-aggregation/package-lock.json
- name: Install token-aggregation dependencies
working-directory: services/token-aggregation
run: npm ci
- name: OMNL reconcile artifact
run: bash scripts/hybx-omnl/omnl-reconcile-artifact.sh
- name: Build token-aggregation
working-directory: services/token-aggregation
run: npm run build
- name: Upload reconcile artifacts
uses: actions/upload-artifact@v4
with:
name: omnl-reconcile-pr-${{ github.run_id }}
path: artifacts/omnl-reconcile/
if-no-files-found: error
retention-days: 14

67
.github/workflows/omnl-reconcile.yml vendored Normal file
View File

@@ -0,0 +1,67 @@
name: OMNL reconcile anchor
on:
workflow_dispatch:
schedule:
# Weekly Monday 07:05 UTC — adjust as needed
- cron: '5 7 * * 1'
pull_request:
branches: [main, develop]
paths:
- 'config/omnl-ipsas-gl-registry.json'
- 'config/omnl-journal-matrix.json'
- 'config/deployment-omnl.example.env'
- 'services/token-aggregation/scripts/omnl-reconcile-report.mjs'
- 'services/token-aggregation/package.json'
- 'scripts/hybx-omnl/omnl-reconcile-artifact.sh'
- '.github/workflows/omnl-reconcile.yml'
- '.github/workflows/hybx-omnl-ts.yml'
push:
branches: [main, develop]
paths:
- 'config/omnl-ipsas-gl-registry.json'
- 'config/omnl-journal-matrix.json'
- 'config/deployment-omnl.example.env'
- 'services/token-aggregation/scripts/omnl-reconcile-report.mjs'
- 'services/token-aggregation/package.json'
- 'scripts/hybx-omnl/omnl-reconcile-artifact.sh'
- '.github/workflows/omnl-reconcile.yml'
- '.github/workflows/hybx-omnl-ts.yml'
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
reconcile:
name: Run omnl:reconcile and upload artifacts
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Validate cross-chain OMNL config
run: node scripts/hybx-omnl/validate-cross-chain-config.mjs
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Generate reconcile artifacts
env:
GITHUB_SHA: ${{ github.sha }}
GITHUB_RUN_ID: ${{ github.run_id }}
GITHUB_REPOSITORY: ${{ github.repository }}
run: bash scripts/hybx-omnl/omnl-reconcile-artifact.sh
- name: Upload OMNL reconcile artifacts
uses: actions/upload-artifact@v4
with:
name: omnl-reconcile-${{ github.run_id }}
path: artifacts/omnl-reconcile/
if-no-files-found: error
retention-days: 90

1
.gitignore vendored
View File

@@ -29,6 +29,7 @@ datadir/
# Foundry
out/
artifacts/
.omnl-poller-state.json
cache/
broadcast/
.cronos-verify/

View File

@@ -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

View File

@@ -0,0 +1,54 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"description": "Planning inputs for funding the Chain 138 ETH-requested PMM surface, executed as WETH pools. The script derives the live USD per ETH anchor from the existing cUSDT/WETH pool and uses the FX assumptions below for non-USD GRU v2 assets.",
"version": "1.0.0",
"updated": "2026-04-20",
"executionConfig": "smom-dbis-138/config/chain138-eth-pmm-pools-execution.json",
"defaultProfile": "light",
"profiles": {
"light": {
"fiatBaseUnits": "100000000000",
"xauBaseUnits": "100000000"
},
"medium": {
"fiatBaseUnits": "500000000000",
"xauBaseUnits": "250000000"
},
"heavy": {
"fiatBaseUnits": "1000000000000",
"xauBaseUnits": "500000000"
}
},
"usdPerUnit": {
"cUSDT": "1.00",
"cUSDC": "1.00",
"cEURC": "1.08",
"cEURT": "1.08",
"cGBPC": "1.27",
"cGBPT": "1.27",
"cAUDC": "0.64",
"cJPYC": "0.0067",
"cCHFC": "1.10",
"cCADC": "0.73"
},
"xauPricing": {
"mode": "derive_from_public_pool",
"poolBaseSymbol": "cXAUC",
"poolQuoteSymbol": "cUSDC",
"fallbackUsdPerUnit": "3300.00",
"appliesTo": [
"cXAUC",
"cXAUT"
]
},
"execution": {
"approveMax": true,
"wrapNativeEthWhenNeeded": true,
"mintMissingBaseWhenOwner": true,
"legacyGasPriceWei": "1000000000",
"addLiquidityGasLimit": "700000",
"approveGasLimit": "120000",
"mintGasLimit": "120000",
"wrapGasLimit": "120000"
}
}

View File

@@ -0,0 +1,142 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"description": "Chain 138 deployment mapping for PMM pools quoted in native ETH across all canonical GRU v2 c* assets. Native ETH is the requested quote surface; execution uses WETH because the current DODO PMM flow is ERC-20 based.",
"version": "1.0.0",
"updated": "2026-04-20",
"chainId": 138,
"sourceOfTruth": {
"tokenConfig": "smom-dbis-138/config/chain138-pmm-pools.json",
"selectionRule": "All symbols in groups.cStarSymbols paired against native ETH request surface."
},
"quoteAsset": {
"requestedSymbol": "ETH",
"requestedAssetType": "native",
"requestedAddress": null,
"executionSymbol": "WETH",
"executionAddress": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
"executionAssetType": "erc20_wrapped_native",
"deploymentRule": "Wrap native ETH to WETH before createPool/addLiquidity/registerPool operations.",
"notes": [
"This mapping intentionally models ETH, not WETH, as the operator-facing quote asset.",
"Current Chain 138 DODO PMM tooling and on-chain pool contracts are ERC-20 based, so execution must use WETH."
]
},
"defaults": {
"lpFeeRate": 3,
"initialPrice": "1000000000000000000",
"kFactor": "500000000000000000",
"enableTwap": false,
"role": "public_routing",
"publicRoutingEnabled": true
},
"pairs": [
{
"baseSymbol": "cUSDT",
"baseAddress": "0x93E66202A11B1772E55407B32B44e5Cd8eda7f22",
"quoteSymbolRequested": "ETH",
"quoteSymbolExecution": "WETH",
"quoteAddressExecution": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
"pairKeyRequested": "cUSDT/ETH",
"pairKeyExecution": "cUSDT/WETH"
},
{
"baseSymbol": "cUSDC",
"baseAddress": "0xf22258f57794CC8E06237084b353Ab30fFfa640b",
"quoteSymbolRequested": "ETH",
"quoteSymbolExecution": "WETH",
"quoteAddressExecution": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
"pairKeyRequested": "cUSDC/ETH",
"pairKeyExecution": "cUSDC/WETH"
},
{
"baseSymbol": "cEURC",
"baseAddress": "0x8085961F9cF02b4d800A3c6d386D31da4B34266a",
"quoteSymbolRequested": "ETH",
"quoteSymbolExecution": "WETH",
"quoteAddressExecution": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
"pairKeyRequested": "cEURC/ETH",
"pairKeyExecution": "cEURC/WETH"
},
{
"baseSymbol": "cEURT",
"baseAddress": "0xdf4b71c61E5912712C1Bdd451416B9aC26949d72",
"quoteSymbolRequested": "ETH",
"quoteSymbolExecution": "WETH",
"quoteAddressExecution": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
"pairKeyRequested": "cEURT/ETH",
"pairKeyExecution": "cEURT/WETH"
},
{
"baseSymbol": "cGBPC",
"baseAddress": "0x003960f16D9d34F2e98d62723B6721Fb92074aD2",
"quoteSymbolRequested": "ETH",
"quoteSymbolExecution": "WETH",
"quoteAddressExecution": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
"pairKeyRequested": "cGBPC/ETH",
"pairKeyExecution": "cGBPC/WETH"
},
{
"baseSymbol": "cGBPT",
"baseAddress": "0x350f54e4D23795f86A9c03988c7135357CCaD97c",
"quoteSymbolRequested": "ETH",
"quoteSymbolExecution": "WETH",
"quoteAddressExecution": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
"pairKeyRequested": "cGBPT/ETH",
"pairKeyExecution": "cGBPT/WETH"
},
{
"baseSymbol": "cAUDC",
"baseAddress": "0xD51482e567c03899eecE3CAe8a058161FD56069D",
"quoteSymbolRequested": "ETH",
"quoteSymbolExecution": "WETH",
"quoteAddressExecution": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
"pairKeyRequested": "cAUDC/ETH",
"pairKeyExecution": "cAUDC/WETH"
},
{
"baseSymbol": "cJPYC",
"baseAddress": "0xEe269e1226a334182aace90056EE4ee5Cc8A6770",
"quoteSymbolRequested": "ETH",
"quoteSymbolExecution": "WETH",
"quoteAddressExecution": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
"pairKeyRequested": "cJPYC/ETH",
"pairKeyExecution": "cJPYC/WETH"
},
{
"baseSymbol": "cCHFC",
"baseAddress": "0x873990849DDa5117d7C644f0aF24370797C03885",
"quoteSymbolRequested": "ETH",
"quoteSymbolExecution": "WETH",
"quoteAddressExecution": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
"pairKeyRequested": "cCHFC/ETH",
"pairKeyExecution": "cCHFC/WETH"
},
{
"baseSymbol": "cCADC",
"baseAddress": "0x54dBd40cF05e15906A2C21f600937e96787f5679",
"quoteSymbolRequested": "ETH",
"quoteSymbolExecution": "WETH",
"quoteAddressExecution": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
"pairKeyRequested": "cCADC/ETH",
"pairKeyExecution": "cCADC/WETH"
},
{
"baseSymbol": "cXAUC",
"baseAddress": "0x290E52a8819A4fbD0714E517225429aA2B70EC6b",
"quoteSymbolRequested": "ETH",
"quoteSymbolExecution": "WETH",
"quoteAddressExecution": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
"pairKeyRequested": "cXAUC/ETH",
"pairKeyExecution": "cXAUC/WETH"
},
{
"baseSymbol": "cXAUT",
"baseAddress": "0x94e408E26c6FD8F4ee00b54dF19082FDA07dC96E",
"quoteSymbolRequested": "ETH",
"quoteSymbolExecution": "WETH",
"quoteAddressExecution": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
"pairKeyRequested": "cXAUT/ETH",
"pairKeyExecution": "cXAUT/WETH"
}
]
}

View File

@@ -0,0 +1,67 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"description": "Execution config for deploying native-ETH-requested PMM pairs on Chain 138. DODO PMM pools are ERC-20 based, so each ETH-requested pair is executed as a WETH pair.",
"version": "1.0.0",
"updated": "2026-04-20",
"chainId": 138,
"defaults": {
"lpFeeRate": 3,
"initialPrice": "1000000000000000000",
"kFactor": "500000000000000000",
"enableTwap": false
},
"tokens": {
"cUSDT": "0x93E66202A11B1772E55407B32B44e5Cd8eda7f22",
"cUSDC": "0xf22258f57794CC8E06237084b353Ab30fFfa640b",
"cEURC": "0x8085961F9cF02b4d800A3c6d386D31da4B34266a",
"cEURT": "0xdf4b71c61E5912712C1Bdd451416B9aC26949d72",
"cGBPC": "0x003960f16D9d34F2e98d62723B6721Fb92074aD2",
"cGBPT": "0x350f54e4D23795f86A9c03988c7135357CCaD97c",
"cAUDC": "0xD51482e567c03899eecE3CAe8a058161FD56069D",
"cJPYC": "0xEe269e1226a334182aace90056EE4ee5Cc8A6770",
"cCHFC": "0x873990849DDa5117d7C644f0aF24370797C03885",
"cCADC": "0x54dBd40cF05e15906A2C21f600937e96787f5679",
"cXAUC": "0x290E52a8819A4fbD0714E517225429aA2B70EC6b",
"cXAUT": "0x94e408E26c6FD8F4ee00b54dF19082FDA07dC96E",
"WETH": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"
},
"groups": {
"cStarSymbols": [
"cUSDT",
"cUSDC",
"cEURC",
"cEURT",
"cGBPC",
"cGBPT",
"cAUDC",
"cJPYC",
"cCHFC",
"cCADC",
"cXAUC",
"cXAUT"
],
"officialStableSymbols": [],
"wethSymbol": "WETH",
"deploy": {
"cStarVsCStar": false,
"cStarVsOfficial": false,
"cStarVsWeth": false,
"officialVsWeth": false
}
},
"explicitPairs": [
{ "baseSymbol": "cUSDT", "quoteSymbol": "WETH" },
{ "baseSymbol": "cUSDC", "quoteSymbol": "WETH" },
{ "baseSymbol": "cEURC", "quoteSymbol": "WETH" },
{ "baseSymbol": "cEURT", "quoteSymbol": "WETH" },
{ "baseSymbol": "cGBPC", "quoteSymbol": "WETH" },
{ "baseSymbol": "cGBPT", "quoteSymbol": "WETH" },
{ "baseSymbol": "cAUDC", "quoteSymbol": "WETH" },
{ "baseSymbol": "cJPYC", "quoteSymbol": "WETH" },
{ "baseSymbol": "cCHFC", "quoteSymbol": "WETH" },
{ "baseSymbol": "cCADC", "quoteSymbol": "WETH" },
{ "baseSymbol": "cXAUC", "quoteSymbol": "WETH" },
{ "baseSymbol": "cXAUT", "quoteSymbol": "WETH" }
],
"plannedPairs": []
}

View File

@@ -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": []
}

View File

@@ -3,6 +3,8 @@ data-path="/data"
genesis-file="/config/genesis.json"
network-id=138
# Placeholder only: node-specific generated configs must advertise the real LAN IP.
# Do not deploy this base template as-is to production sentries/members.
p2p-host="0.0.0.0"
p2p-port=30303

View File

@@ -0,0 +1,47 @@
# Besu Configuration for ALLTRA / HYBX RPC Nodes
# Placeholder only: node-specific generated configs must advertise the real LAN IP.
# Do not deploy this base template as-is without substituting the real advertised host.
data-path="/data/besu"
genesis-file="/genesis/genesis.json"
network-id=138
p2p-host="0.0.0.0"
p2p-port=30303
sync-mode="FULL"
data-storage-format="FOREST"
rpc-http-enabled=true
rpc-http-host="0.0.0.0"
rpc-http-port=8545
rpc-http-api=["ETH","NET","WEB3","ADMIN","PERSONAL","MINER","DEBUG"]
rpc-http-cors-origins=["*"]
rpc-http-api-enable-unsafe-txsigning=true
rpc-ws-enabled=true
rpc-ws-host="0.0.0.0"
rpc-ws-port=8546
rpc-ws-api=["ETH","NET","WEB3","ADMIN","PERSONAL","MINER","DEBUG"]
graphql-http-enabled=true
graphql-http-host="0.0.0.0"
graphql-http-port=8547
metrics-enabled=true
metrics-host="0.0.0.0"
metrics-port=9545
metrics-push-enabled=false
logging="INFO"
log-destination="CONSOLE"
permissions-nodes-config-file-enabled=true
permissions-nodes-config-file="/var/lib/besu/permissions/permissions-nodes.toml"
permissions-accounts-config-file-enabled=true
permissions-accounts-config-file="/permissions/permissions-accounts.toml"
bootnodes=[]
static-nodes-file="/var/lib/besu/static-nodes.json"
discovery-enabled=false
max-peers=40

View File

@@ -3,6 +3,8 @@ data-path="/data/besu"
genesis-file="/genesis/genesis.json"
network-id=138
# Placeholder only: node-specific generated configs must advertise the real LAN IP.
# Do not deploy this base template as-is to production core/admin RPC nodes.
p2p-host="0.0.0.0"
p2p-port=30303

View File

@@ -5,6 +5,8 @@ data-path="/data/besu"
genesis-file="/genesis/genesis.json"
network-id=138
# Placeholder only: node-specific generated configs must advertise the real LAN IP.
# Do not deploy this base template as-is to production public RPC nodes.
p2p-host="0.0.0.0"
p2p-port=30303

View File

@@ -6,6 +6,8 @@ data-path="/data/besu"
genesis-file="/genesis/genesis.json"
network-id=138
# Placeholder only: node-specific generated configs must advertise the real LAN IP.
# Do not deploy this base template as-is to production thirdweb admin/core RPC nodes.
p2p-host="0.0.0.0"
p2p-port=30303
@@ -35,8 +37,15 @@ permissions-nodes-config-file="/var/lib/besu/permissions/permissions-nodes.toml"
permissions-accounts-config-file-enabled=true
permissions-accounts-config-file="/permissions/permissions-accounts.toml"
tx-pool-max-size=8192
tx-pool-limit-by-account-percentage=0.5
# Thirdweb (2103) only: allow at most ONE queued “next” future tx per sender in the layered pool.
# This rejects deep nonce gaps and huge sequential queues (e.g. 15) when chain next is 1, instead of
# retaining them until gossip refills. Use 1, not 0: tx-pool-max-future-by-sender=0 can trigger Besu
# LayeredPendingTransactions IndexOutOfBounds (SparseTransactions) on block import.
# Other RPC nodes (2101, 2201) stay at higher limits — see config-rpc-core / config-rpc-public.
tx-pool=layered
tx-pool-layer-max-capacity=12500000
tx-pool-max-prioritized=2000
tx-pool-max-future-by-sender=1
tx-pool-price-bump=10
bootnodes=[]

View File

@@ -0,0 +1,49 @@
# Besu Configuration for Promoted Sentry Nodes
# Intended for the more featureful sentry lanes such as HYBX / Thirdweb sentries.
# Placeholder only: node-specific generated configs must advertise the real LAN IP.
# Do not deploy this base template as-is without substituting the real advertised host.
data-path="/data/besu"
genesis-file="/genesis/genesis.json"
network-id=138
p2p-host="0.0.0.0"
p2p-port=30303
sync-mode="FULL"
data-storage-format="FOREST"
rpc-http-enabled=true
rpc-http-host="0.0.0.0"
rpc-http-port=8545
rpc-http-api=["ETH","NET","WEB3","ADMIN","PERSONAL","MINER","DEBUG"]
rpc-http-cors-origins=["*"]
rpc-http-api-enable-unsafe-txsigning=true
rpc-ws-enabled=true
rpc-ws-host="0.0.0.0"
rpc-ws-port=8546
rpc-ws-api=["ETH","NET","WEB3","ADMIN","PERSONAL","MINER","DEBUG"]
graphql-http-enabled=true
graphql-http-host="0.0.0.0"
graphql-http-port=8547
metrics-enabled=true
metrics-host="0.0.0.0"
metrics-port=9545
metrics-push-enabled=false
logging="INFO"
log-destination="CONSOLE"
permissions-nodes-config-file-enabled=true
permissions-nodes-config-file="/var/lib/besu/permissions/permissions-nodes.toml"
permissions-accounts-config-file-enabled=false
tx-pool-max-future-by-sender=1
bootnodes=[]
static-nodes-file="/var/lib/besu/static-nodes.json"
discovery-enabled=true
max-peers=40

View File

@@ -3,6 +3,8 @@ data-path="/data"
genesis-file="/config/genesis.json"
network-id=138
# Placeholder only: node-specific generated configs must advertise the real LAN IP.
# Do not deploy this base template as-is to production validators.
p2p-host="0.0.0.0"
p2p-port=30303

View File

@@ -0,0 +1,40 @@
# HYBX OMNL — example deployment (copy; do not commit real secrets)
# Chain / RPC
CHAIN_ID=138
OMNL_RPC_URL=https://
OMNL_MIRROR_RPC_URL=https://
# Contracts (post-deploy)
OMNL_INSTRUMENT_REGISTRY=
OMNL_RESERVE_COMMITMENT_STORE=
OMNL_COMPLIANCE_CORE=
OMNL_CIRCUIT_BREAKER=
OMNL_MIRROR_RECEIVER=
OMNL_MIRROR_COORDINATOR=
OMNL_ZK_VERIFIER=0x0000000000000000000000000000000000000000
# CCIP
OMNL_CCIP_ROUTER=
OMNL_CCIP_DEST_CHAIN_SELECTOR=
OMNL_CCIP_FEE_TOKEN=0x0000000000000000000000000000000000000000
# Fineract / IPSAS
OMNL_FINERACT_BASE_URL=
OMNL_FINERACT_TENANT=
# Use OMNL_FINERACT_USER or legacy OMNL_FINERACT_USERNAME (same role)
OMNL_FINERACT_USER=
OMNL_FINERACT_USERNAME=
OMNL_FINERACT_PASSWORD=
OMNL_FINERACT_GL_PAGE_LIMIT=200
# API / ops
ENABLE_OMNL_EVENT_POLLER=true
OMNL_POLLER_STATE_PATH=
OMNL_WEBHOOK_URLS=
OMNL_WEBHOOK_SECRET=
OMNL_API_KEY=
OMNL_DASHBOARD_TOKEN=
OMNL_RATE_LIMIT_MAX=30
OMNL_RATE_LIMIT_WINDOW_MS=60000
OMNL_IPSAS_GL_REGISTRY=
OMNL_JOURNAL_MATRIX_PATH=

View File

@@ -0,0 +1,548 @@
{
"bundleName": "gas-pmm-all-22-pools",
"description": "Execution bundle for the remaining 22 planned gas-family DODO PMM placeholder rows across Ethereum mainnet and public networks.",
"poolDefaults": {
"lpFeeRate": 3,
"selfQuoteInitialPrice1e18": "1000000000000000000",
"k": "500000000000000000",
"enableTwap": true
},
"chains": [
{
"chainId": 1,
"network": "Ethereum Mainnet",
"chainKey": "ethereum",
"rpcEnv": [
"ETHEREUM_MAINNET_RPC",
"MAINNET_RPC_URL",
"MAINNET_RPC"
],
"integrationEnv": "CHAIN_1_DODO_PMM_INTEGRATION",
"dodoVendingMachineEnv": [
"ETHEREUM_DODO_VENDING_MACHINE_ADDRESS",
"MAINNET_DODO_VENDING_MACHINE_ADDRESS"
],
"baseToken": {
"symbol": "cWETH",
"address": "0xf6dc5587e18f27adff60e303fdd98f35b50fa8a5"
},
"wrappedNativeQuote": {
"symbol": "WETH",
"env": [
"WETH9_MAINNET",
"WETH_MAINNET"
],
"default": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
"note": "Canonical mainnet WETH."
},
"stableQuote": {
"symbol": "USDC",
"env": [
"ETHEREUM_OFFICIAL_USDC_ADDRESS",
"MAINNET_OFFICIAL_USDC_ADDRESS",
"OFFICIAL_USDC_MAINNET"
],
"default": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
"note": "Canonical mainnet USDC."
},
"pairs": [
{
"pair": "cWETH/WETH",
"baseSymbol": "cWETH",
"quoteSymbol": "WETH",
"expectedPoolAddress": "0xd011000000000000000000000000000000000001"
},
{
"pair": "cWETH/USDC",
"baseSymbol": "cWETH",
"quoteSymbol": "USDC",
"expectedPoolAddress": "0xd012000000000000000000000000000000000001"
}
]
},
{
"chainId": 10,
"network": "Optimism",
"chainKey": "optimism",
"rpcEnv": [
"OPTIMISM_MAINNET_RPC",
"OPTIMISM_RPC_URL"
],
"integrationEnv": "CHAIN_10_DODO_PMM_INTEGRATION",
"dodoVendingMachineEnv": [
"OPTIMISM_DODO_VENDING_MACHINE_ADDRESS"
],
"baseToken": {
"symbol": "cWETHL2",
"address": "0x95007ec50d0766162f77848edf7bdc4eba147fb4"
},
"wrappedNativeQuote": {
"symbol": "WETH",
"env": [
"OPTIMISM_WETH_ADDRESS"
],
"default": "0x4200000000000000000000000000000000000006",
"note": "Optimism canonical WETH."
},
"stableQuote": {
"symbol": "USDC",
"env": [
"OPTIMISM_OFFICIAL_USDC_ADDRESS"
],
"default": "0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85",
"note": "Optimism native USDC."
},
"pairs": [
{
"pair": "cWETHL2/WETH",
"baseSymbol": "cWETHL2",
"quoteSymbol": "WETH",
"expectedPoolAddress": "0xd02100000000000000000000000000000000000a"
},
{
"pair": "cWETHL2/USDC",
"baseSymbol": "cWETHL2",
"quoteSymbol": "USDC",
"expectedPoolAddress": "0xd02200000000000000000000000000000000000a"
}
]
},
{
"chainId": 25,
"network": "Cronos",
"chainKey": "cronos",
"rpcEnv": [
"CRONOS_RPC_URL",
"CRONOS_RPC",
"CRONOS_MAINNET_RPC"
],
"integrationEnv": "CHAIN_25_DODO_PMM_INTEGRATION",
"dodoVendingMachineEnv": [
"CRONOS_DODO_VENDING_MACHINE_ADDRESS"
],
"baseToken": {
"symbol": "cWCRO",
"address": "0x9b10eb0f77c45322dbd1fcb07176fd9a7609c164"
},
"wrappedNativeQuote": {
"symbol": "WCRO",
"env": [
"CRONOS_WCRO_ADDRESS",
"WCRO_ADDRESS"
],
"default": "0x5C7F8A570d578ED84E63fdFA7b1eE72dEae1AE23",
"note": "Cronos wrapped CRO from the repo's multichain trustless deployment defaults."
},
"stableQuote": {
"symbol": "USDT",
"env": [
"CRONOS_OFFICIAL_USDT_ADDRESS"
],
"default": "0x66e428c3f67a68878562e79A0234c1F83c208770",
"note": "Cronos USDT corrected to the official token list value already adopted in repo state."
},
"pairs": [
{
"pair": "cWCRO/WCRO",
"baseSymbol": "cWCRO",
"quoteSymbol": "WCRO",
"expectedPoolAddress": "0xd061000000000000000000000000000000000019"
},
{
"pair": "cWCRO/USDT",
"baseSymbol": "cWCRO",
"quoteSymbol": "USDT",
"expectedPoolAddress": "0xd062000000000000000000000000000000000019"
}
]
},
{
"chainId": 56,
"network": "BSC (BNB Chain)",
"chainKey": "bsc",
"rpcEnv": [
"BSC_RPC_URL",
"BSC_MAINNET_RPC"
],
"integrationEnv": "CHAIN_56_DODO_PMM_INTEGRATION",
"dodoVendingMachineEnv": [
"BSC_DODO_VENDING_MACHINE_ADDRESS"
],
"baseToken": {
"symbol": "cWBNB",
"address": "0x179034a08ac2c9c35d2e41239f68c79dca6f18fa"
},
"wrappedNativeQuote": {
"symbol": "WBNB",
"env": [
"BSC_WBNB_ADDRESS",
"WBNB_ADDRESS",
"BSC_WRAPPED_NATIVE_ADDRESS"
],
"default": "0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c",
"note": "BSC canonical WBNB."
},
"stableQuote": {
"symbol": "USDT",
"env": [
"BSC_OFFICIAL_USDT_ADDRESS"
],
"default": "0x55d398326f99059fF775485246999027B3197955",
"note": "BSC canonical USDT."
},
"pairs": [
{
"pair": "cWBNB/WBNB",
"baseSymbol": "cWBNB",
"quoteSymbol": "WBNB",
"expectedPoolAddress": "0xd031000000000000000000000000000000000038"
},
{
"pair": "cWBNB/USDT",
"baseSymbol": "cWBNB",
"quoteSymbol": "USDT",
"expectedPoolAddress": "0xd032000000000000000000000000000000000038"
}
]
},
{
"chainId": 100,
"network": "Gnosis Chain",
"chainKey": "gnosis",
"rpcEnv": [
"GNOSIS_RPC",
"GNOSIS_MAINNET_RPC"
],
"integrationEnv": "CHAIN_100_DODO_PMM_INTEGRATION",
"dodoVendingMachineEnv": [
"GNOSIS_DODO_VENDING_MACHINE_ADDRESS"
],
"baseToken": {
"symbol": "cWXDAI",
"address": "0x9f833b4f1012f52eb3317b09922a79c6edfca77d"
},
"wrappedNativeQuote": {
"symbol": "WXDAI",
"env": [
"WETH9_GNOSIS",
"GNOSIS_WXDAI_ADDRESS",
"WXDAI_ADDRESS"
],
"default": "0xe91D153E0b41518A2Ce8Dd3D7944Fa863463a97d",
"note": "Gnosis wrapped xDAI from the config-ready chain env example."
},
"stableQuote": {
"symbol": "USDC",
"env": [
"GNOSIS_OFFICIAL_USDC_ADDRESS"
],
"default": "0xDDAfbb505ad214D7b80b1f830fcCc89B60fb7A83",
"note": "Gnosis canonical bridged USDC."
},
"pairs": [
{
"pair": "cWXDAI/WXDAI",
"baseSymbol": "cWXDAI",
"quoteSymbol": "WXDAI",
"expectedPoolAddress": "0xd071000000000000000000000000000000000064"
},
{
"pair": "cWXDAI/USDC",
"baseSymbol": "cWXDAI",
"quoteSymbol": "USDC",
"expectedPoolAddress": "0xd072000000000000000000000000000000000064"
}
]
},
{
"chainId": 137,
"network": "Polygon",
"chainKey": "polygon",
"rpcEnv": [
"POLYGON_MAINNET_RPC",
"POLYGON_RPC_URL"
],
"integrationEnv": "CHAIN_137_DODO_PMM_INTEGRATION",
"dodoVendingMachineEnv": [
"POLYGON_DODO_VENDING_MACHINE_ADDRESS"
],
"baseToken": {
"symbol": "cWPOL",
"address": "0x25980244aacecb6d8c4b887261ed27f87cb2fc73"
},
"wrappedNativeQuote": {
"symbol": "WPOL",
"env": [
"POLYGON_WPOL_ADDRESS",
"POLYGON_WRAPPED_NATIVE_ADDRESS",
"WPOL_ADDRESS"
],
"default": "0x0d500B1d8E8eF31E21C99d1Db9A6444d3ADf1270",
"note": "Polygon native wrapped token contract; on-chain symbol may still read WMATIC."
},
"stableQuote": {
"symbol": "USDC",
"env": [
"POLYGON_OFFICIAL_USDC_ADDRESS"
],
"default": "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359",
"note": "Polygon native USDC."
},
"pairs": [
{
"pair": "cWPOL/WPOL",
"baseSymbol": "cWPOL",
"quoteSymbol": "WPOL",
"expectedPoolAddress": "0xd041000000000000000000000000000000000089"
},
{
"pair": "cWPOL/USDC",
"baseSymbol": "cWPOL",
"quoteSymbol": "USDC",
"expectedPoolAddress": "0xd042000000000000000000000000000000000089"
}
]
},
{
"chainId": 8453,
"network": "Base",
"chainKey": "base",
"rpcEnv": [
"BASE_MAINNET_RPC",
"BASE_RPC_URL"
],
"integrationEnv": "CHAIN_8453_DODO_PMM_INTEGRATION",
"dodoVendingMachineEnv": [
"BASE_DODO_VENDING_MACHINE_ADDRESS"
],
"baseToken": {
"symbol": "cWETHL2",
"address": "0x2a0840e5117683b11682ac46f5cf5621e67269e3"
},
"wrappedNativeQuote": {
"symbol": "WETH",
"env": [
"BASE_WETH_ADDRESS"
],
"default": "0x4200000000000000000000000000000000000006",
"note": "Base canonical WETH."
},
"stableQuote": {
"symbol": "USDC",
"env": [
"BASE_OFFICIAL_USDC_ADDRESS"
],
"default": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
"note": "Base native USDC."
},
"pairs": [
{
"pair": "cWETHL2/WETH",
"baseSymbol": "cWETHL2",
"quoteSymbol": "WETH",
"expectedPoolAddress": "0xd021000000000000000000000000000000002105"
},
{
"pair": "cWETHL2/USDC",
"baseSymbol": "cWETHL2",
"quoteSymbol": "USDC",
"expectedPoolAddress": "0xd022000000000000000000000000000000002105"
}
]
},
{
"chainId": 42161,
"network": "Arbitrum One",
"chainKey": "arbitrum",
"rpcEnv": [
"ARBITRUM_MAINNET_RPC",
"ARBITRUM_RPC"
],
"integrationEnv": "CHAIN_42161_DODO_PMM_INTEGRATION",
"dodoVendingMachineEnv": [
"ARBITRUM_DODO_VENDING_MACHINE_ADDRESS"
],
"baseToken": {
"symbol": "cWETHL2",
"address": "0xe27be001bc55cb2a8ed5ba5a62c834ca135244a3"
},
"wrappedNativeQuote": {
"symbol": "WETH",
"env": [
"ARBITRUM_WETH_ADDRESS"
],
"default": "0x82aF49447D8a07e3bd95BD0d56f35241523fBab1",
"note": "Arbitrum canonical WETH."
},
"stableQuote": {
"symbol": "USDC",
"env": [
"ARBITRUM_OFFICIAL_USDC_ADDRESS"
],
"default": "0xaf88d065e77c8cC2239327C5EDb3A432268e5831",
"note": "Arbitrum native USDC."
},
"pairs": [
{
"pair": "cWETHL2/WETH",
"baseSymbol": "cWETHL2",
"quoteSymbol": "WETH",
"expectedPoolAddress": "0xd02100000000000000000000000000000000a4b1"
},
{
"pair": "cWETHL2/USDC",
"baseSymbol": "cWETHL2",
"quoteSymbol": "USDC",
"expectedPoolAddress": "0xd02200000000000000000000000000000000a4b1"
}
]
},
{
"chainId": 42220,
"network": "Celo",
"chainKey": "celo",
"rpcEnv": [
"CELO_RPC",
"CELO_MAINNET_RPC"
],
"integrationEnv": "CHAIN_42220_DODO_PMM_INTEGRATION",
"dodoVendingMachineEnv": [
"CELO_DODO_VENDING_MACHINE_ADDRESS"
],
"baseToken": {
"symbol": "cWCELO",
"address": "0xb0fa7ec4123c7c275b3a89d9239569707ea3c66a"
},
"wrappedNativeQuote": {
"symbol": "WCELO",
"env": [
"WETH9_CELO",
"CELO_WCELO_ADDRESS",
"WCELO_ADDRESS"
],
"default": "0x2021B12D8138e2D63cF0895eccABC0DFc92416c6",
"note": "Celo wrapped CELO from the config-ready chain env example."
},
"stableQuote": {
"symbol": "USDC",
"env": [
"CELO_OFFICIAL_USDC_ADDRESS"
],
"default": "0xcebA9300f2b948710d2653dD7B07f33A8B32118C",
"note": "Celo USDC address currently used by the canonical deployment graph."
},
"pairs": [
{
"pair": "cWCELO/WCELO",
"baseSymbol": "cWCELO",
"quoteSymbol": "WCELO",
"expectedPoolAddress": "0xd08100000000000000000000000000000000a4ec"
},
{
"pair": "cWCELO/USDC",
"baseSymbol": "cWCELO",
"quoteSymbol": "USDC",
"expectedPoolAddress": "0xd08200000000000000000000000000000000a4ec"
}
]
},
{
"chainId": 43114,
"network": "Avalanche C-Chain",
"chainKey": "avalanche",
"rpcEnv": [
"AVALANCHE_RPC_URL",
"AVALANCHE_MAINNET_RPC",
"AVALANCHE_RPC"
],
"integrationEnv": "CHAIN_43114_DODO_PMM_INTEGRATION",
"dodoVendingMachineEnv": [
"AVALANCHE_DODO_VENDING_MACHINE_ADDRESS"
],
"baseToken": {
"symbol": "cWAVAX",
"address": "0xe1d4aee2ef8f48a20338935188a8fe7f7c7de7d0"
},
"wrappedNativeQuote": {
"symbol": "WAVAX",
"env": [
"AVALANCHE_WAVAX_ADDRESS",
"WAVAX_ADDRESS"
],
"default": "0xB31f66AA3C1e785363F0875A1B74E27b85FD66c7",
"note": "Avalanche canonical WAVAX."
},
"stableQuote": {
"symbol": "USDC",
"env": [
"AVALANCHE_OFFICIAL_USDC_ADDRESS"
],
"default": "0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E",
"note": "Avalanche native USDC."
},
"pairs": [
{
"pair": "cWAVAX/WAVAX",
"baseSymbol": "cWAVAX",
"quoteSymbol": "WAVAX",
"expectedPoolAddress": "0xd05100000000000000000000000000000000a86a"
},
{
"pair": "cWAVAX/USDC",
"baseSymbol": "cWAVAX",
"quoteSymbol": "USDC",
"expectedPoolAddress": "0xd05200000000000000000000000000000000a86a"
}
]
},
{
"chainId": 1111,
"network": "Wemix",
"chainKey": "wemix",
"rpcEnv": [
"WEMIX_RPC",
"WEMIX_MAINNET_RPC"
],
"integrationEnv": "CHAIN_1111_DODO_PMM_INTEGRATION",
"dodoVendingMachineEnv": [
"WEMIX_DODO_VENDING_MACHINE_ADDRESS"
],
"baseToken": {
"symbol": "cWWEMIX",
"address": "0xc111000000000000000000000000000000000457"
},
"wrappedNativeQuote": {
"symbol": "WWEMIX",
"env": [
"WETH9_WEMIX",
"WEMIX_WWEMIX_ADDRESS",
"WWEMIX_ADDRESS"
],
"default": "0x7D72b22a74A216Af4a002a1095C8C707d6eC1C5f",
"note": "Wemix wrapped native confirmed in the repo's verification docs."
},
"stableQuote": {
"symbol": "USDC",
"env": [
"WEMIX_OFFICIAL_USDC_ADDRESS"
],
"default": "0xE3F5a90F9cb311505cd691a46596599aA1A0AD7D",
"note": "Recommended correction from the Wemix token verification document; this supersedes the placeholder-like master-map value."
},
"pairs": [
{
"pair": "cWWEMIX/WWEMIX",
"baseSymbol": "cWWEMIX",
"quoteSymbol": "WWEMIX",
"expectedPoolAddress": "0xd091000000000000000000000000000000000457"
},
{
"pair": "cWWEMIX/USDC",
"baseSymbol": "cWWEMIX",
"quoteSymbol": "USDC",
"expectedPoolAddress": "0xd092000000000000000000000000000000000457"
}
]
}
]
}

View File

@@ -0,0 +1,20 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"description": "Map logical lineId to M0/M1 token addresses per chain for aggregated PoR (sum totalSupply across 138 + 651940). Copy to hybx-omnl-cross-chain-lines.json and set OMNL_CROSS_CHAIN_CONFIG.",
"version": "1.0.0",
"lines": [
{
"lineId": "0x0000000000000000000000000000000000000000000000000000000000000000",
"chains": {
"138": {
"tokenM0": "0x0000000000000000000000000000000000000000",
"tokenM1": "0x0000000000000000000000000000000000000000"
},
"651940": {
"tokenM0": "0x0000000000000000000000000000000000000000",
"tokenM1": "0x0000000000000000000000000000000000000000"
}
}
}
]
}

View File

@@ -0,0 +1,4 @@
{
"version": "1.0.0",
"lines": []
}

View File

@@ -0,0 +1,22 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"description": "HYBX OMNL deterministic policy constants (must match PolicyMath.sol / ComplianceCore.sol)",
"version": "1.0.0",
"m0BackingNumerator": 12,
"m0BackingDenominator": 10,
"m1ExpansionNumerator": 5,
"m1ExpansionDenominator": 1,
"rounding": {
"minReserves": "ceil_div",
"maxM1": "exact_mul"
},
"aggregationScope": "per_line",
"chains": {
"primaryReserveWriter": 138,
"mirrorTarget": 651940
},
"defaultTtlSeconds": {
"fiat": 86400,
"xau": 14400
}
}

View File

@@ -0,0 +1,60 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"description": "IPSAS-aligned GL codes for OMNL Hybx (Fineract). Pairs must match omnl-journal-matrix.json postings. Source: docs/04-configuration/mifos-omnl-central-bank/OMNL_JOURNAL_LEDGER_MATRIX.md",
"version": "1.0.0",
"currencyCode": "USD",
"accounts": [
{
"glCode": "1000",
"name": "USD Settlement & Reserve Assets",
"fineractType": "ASSET",
"usage": "Cash and cash equivalents (IPSAS 2); settlement balances",
"ipsasStandards": ["IPSAS 2", "IPSAS 28", "IPSAS 29"],
"roles": ["settlement", "cash_equivalent"]
},
{
"glCode": "1050",
"name": "USD Treasury Conversion Reserve (M0)",
"fineractType": "ASSET",
"usage": "Reserve backing M1 capacity; financial asset",
"ipsasStandards": ["IPSAS 28", "IPSAS 29"],
"roles": ["m0_reserve", "treasury_reserve"]
},
{
"glCode": "2000",
"name": "USD Central Deposits (M1)",
"fineractType": "LIABILITY",
"usage": "Demand deposits; financial liability",
"ipsasStandards": ["IPSAS 28", "IPSAS 29"],
"roles": ["m1_liability", "demand_deposit"]
},
{
"glCode": "2100",
"name": "USD Restricted / Escrow (M1)",
"fineractType": "LIABILITY",
"usage": "Restricted liabilities; escrow",
"ipsasStandards": ["IPSAS 28", "IPSAS 29"],
"roles": ["m1_restricted", "escrow"]
},
{
"glCode": "3000",
"name": "Opening Balance Control",
"fineractType": "EQUITY",
"usage": "Migration control; equity",
"ipsasStandards": ["IPSAS 1", "IPSAS 3"],
"roles": ["equity", "migration_control"]
}
],
"allowedJournalPairs": [
{ "debitGlCode": "1000", "creditGlCode": "2000", "ipsasRef": "IPSAS 3, 28", "memo": "T-001" },
{ "debitGlCode": "1050", "creditGlCode": "2000", "ipsasRef": "IPSAS 28, 29", "memo": "T-001B" },
{ "debitGlCode": "2000", "creditGlCode": "2000", "ipsasRef": "IPSAS 28", "memo": "T-002A" },
{ "debitGlCode": "2000", "creditGlCode": "2100", "ipsasRef": "IPSAS 28", "memo": "T-002B" }
],
"monetaryLayerHints": {
"m0_reserve": { "primaryGlCodes": ["1050"], "ipsasNarrative": "Treasury / M0 reserve assets (IPSAS 28, 29)" },
"m1_liability": { "primaryGlCodes": ["2000", "2100"], "ipsasNarrative": "Financial liabilities — deposits (IPSAS 28, 29)" },
"settlement": { "primaryGlCodes": ["1000"], "ipsasNarrative": "Cash and cash equivalents (IPSAS 2)" },
"equity": { "primaryGlCodes": ["3000"], "ipsasNarrative": "Equity / control (IPSAS 1)" }
}
}

View File

@@ -0,0 +1,99 @@
{
"description": "OMNL journal entries for API posting to OMNL Hybx (Fineract). Head Office and entities 28 per Migration Memorandum. officeId=1 for all; narrative identifies entity. IPSAS-aligned.",
"source": "OMNL_JOURNAL_LEDGER_MATRIX.md",
"currencyCode": "USD",
"dateFormat": "yyyy-MM-dd",
"locale": "en",
"entries": [
{
"memo": "T-001",
"officeId": 1,
"debitGlCode": "1000",
"creditGlCode": "2000",
"amount": 900000000000,
"narrative": "Opening Balance Migration (Head Office)",
"ipsasRef": "IPSAS 3, 28"
},
{
"memo": "T-001B",
"officeId": 1,
"debitGlCode": "1050",
"creditGlCode": "2000",
"amount": 250000000000,
"narrative": "Treasury Conversion — Transfer to Reserve (M0); Head Office",
"ipsasRef": "IPSAS 28, 29"
},
{
"memo": "T-002A",
"officeId": 1,
"debitGlCode": "2000",
"creditGlCode": "2000",
"amount": 2900000000,
"narrative": "Shamrayan Available (M1) — Office 2",
"ipsasRef": "IPSAS 28"
},
{
"memo": "T-002B",
"officeId": 1,
"debitGlCode": "2000",
"creditGlCode": "2100",
"amount": 2100000000,
"narrative": "Shamrayan Restricted — Office 2",
"ipsasRef": "IPSAS 28"
},
{
"memo": "T-003",
"officeId": 1,
"debitGlCode": "2000",
"creditGlCode": "2100",
"amount": 350000000000,
"narrative": "HYBX Capitalization Escrow — Office 3",
"ipsasRef": "IPSAS 28"
},
{
"memo": "T-004",
"officeId": 1,
"debitGlCode": "2000",
"creditGlCode": "2000",
"amount": 5000000000,
"narrative": "TAJ Allocation (M1) — Office 4",
"ipsasRef": "IPSAS 28"
},
{
"memo": "T-005",
"officeId": 1,
"debitGlCode": "2000",
"creditGlCode": "2000",
"amount": 5000000000,
"narrative": "Aseret Allocation (M1) — Office 5",
"ipsasRef": "IPSAS 28"
},
{
"memo": "T-006",
"officeId": 1,
"debitGlCode": "2000",
"creditGlCode": "2000",
"amount": 5000000000,
"narrative": "Mann Li Allocation (M1) — Office 6",
"ipsasRef": "IPSAS 28"
},
{
"memo": "T-007",
"officeId": 1,
"debitGlCode": "2000",
"creditGlCode": "2000",
"amount": 50000000000,
"narrative": "OSJ Allocation (M1) — Office 7",
"ipsasRef": "IPSAS 28"
},
{
"memo": "T-008",
"officeId": 1,
"debitGlCode": "2000",
"creditGlCode": "2000",
"amount": 50000000000,
"narrative": "Alltra Allocation (M1) — Office 8",
"ipsasRef": "IPSAS 28"
}
]
}

View File

@@ -0,0 +1,97 @@
{
"bundleName": "optimism-cronos-dodo-4-pools",
"description": "Focused execution bundle for the four remaining configured_no_code DODO PMM pools on Optimism and Cronos.",
"poolDefaults": {
"lpFeeRate": 3,
"initialPrice1e18": "1000000000000000000",
"k": "500000000000000000",
"enableTwap": true
},
"chains": [
{
"chainId": 10,
"network": "Optimism",
"rpcEnv": [
"OPTIMISM_MAINNET_RPC",
"OPTIMISM_RPC_URL"
],
"integrationEnv": "CHAIN_10_DODO_PMM_INTEGRATION",
"dodoVendingMachineEnv": "OPTIMISM_DODO_VENDING_MACHINE_ADDRESS",
"official": {
"USDC": {
"env": "OPTIMISM_OFFICIAL_USDC_ADDRESS",
"default": "0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85"
},
"USDT": {
"env": "OPTIMISM_OFFICIAL_USDT_ADDRESS",
"default": "0x94b008aA00579c1307B0EF2c499aD98a8ce58e58"
}
},
"compliant": {
"cWUSDC": {
"env": "CWUSDC_OPTIMISM"
},
"cWUSDT": {
"env": "CWUSDT_OPTIMISM"
}
},
"pairs": [
{
"pair": "cWUSDC/USDC",
"baseSymbol": "cWUSDC",
"quoteSymbol": "USDC",
"expectedPoolAddress": "0x022a8835b5c8fd6714cE33c783a426398468702B"
},
{
"pair": "cWUSDT/USDT",
"baseSymbol": "cWUSDT",
"quoteSymbol": "USDT",
"expectedPoolAddress": "0x0630059fc9a629DABAC1244c9f021A33A71B098f"
}
]
},
{
"chainId": 25,
"network": "Cronos",
"rpcEnv": [
"CRONOS_RPC_URL",
"CRONOS_RPC",
"CRONOS_MAINNET_RPC"
],
"integrationEnv": "CHAIN_25_DODO_PMM_INTEGRATION",
"dodoVendingMachineEnv": "CRONOS_DODO_VENDING_MACHINE_ADDRESS",
"official": {
"USDC": {
"env": "CRONOS_OFFICIAL_USDC_ADDRESS",
"default": "0xc21223249CA28397B4B6541dfFaEcC539BfF0c59"
},
"USDT": {
"env": "CRONOS_OFFICIAL_USDT_ADDRESS",
"default": "0x66e428c3f67a68878562e79A0234c1F83c208770"
}
},
"compliant": {
"cWUSDC": {
"env": "CWUSDC_CRONOS"
},
"cWUSDT": {
"env": "CWUSDT_CRONOS"
}
},
"pairs": [
{
"pair": "cWUSDC/USDC",
"baseSymbol": "cWUSDC",
"quoteSymbol": "USDC",
"expectedPoolAddress": "0x72c50bb2c621a2C10E162776D0D210d3C9f8Ac02"
},
{
"pair": "cWUSDT/USDT",
"baseSymbol": "cWUSDT",
"quoteSymbol": "USDT",
"expectedPoolAddress": "0xb4F3d4C8995032690837543438ac40BA5cbfd8Fe"
}
]
}
]
}

View File

@@ -0,0 +1,162 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {InstrumentRegistry} from "./InstrumentRegistry.sol";
import {ReserveCommitmentStore} from "./ReserveCommitmentStore.sol";
import {OMNLCircuitBreaker} from "./OMNLCircuitBreaker.sol";
import {PolicyMath} from "./PolicyMath.sol";
/**
* @title ComplianceCore
* @notice Aggregates on-chain supply + attested reserves; enforces policy; emits audit events.
* @dev Integrated minters call assertCanMintM0/M1 before minting.
*/
contract ComplianceCore {
InstrumentRegistry public immutable registry;
ReserveCommitmentStore public immutable reserves;
OMNLCircuitBreaker public immutable breakers;
event ComplianceSnapshot(
bytes32 indexed lineId,
uint256 s0,
uint256 s1,
uint256 r,
uint256 validUntil,
bytes32 evidenceHash,
bytes32 merkleRoot,
bool m0Ok,
bool m1Ok,
bool attestationStale,
bool policyOk,
bool reportingCompliant
);
error ComplianceBlocked(bytes32 lineId, string reason);
error UnknownLine(bytes32 lineId);
constructor(address registry_, address reserves_, address breakers_) {
require(registry_ != address(0) && reserves_ != address(0) && breakers_ != address(0), "ComplianceCore: zero");
registry = InstrumentRegistry(registry_);
reserves = ReserveCommitmentStore(reserves_);
breakers = OMNLCircuitBreaker(breakers_);
}
function _loadSupplies(bytes32 lineId) internal view returns (uint256 s0, uint256 s1) {
InstrumentRegistry.Line memory line = registry.getLine(lineId);
if (line.tokenM0 == address(0)) revert UnknownLine(lineId);
s0 = IERC20(line.tokenM0).totalSupply();
s1 = IERC20(line.tokenM1).totalSupply();
}
function _stale(uint256 validUntil) internal view returns (bool) {
return block.timestamp > validUntil;
}
/**
* @notice Full deterministic compliance view for dashboards and off-chain API.
* @dev reportingCompliant is false when attestation is stale or breakers trip, even if pure math (policyOk) holds.
*/
function getCompliance(bytes32 lineId)
external
view
returns (
uint256 s0,
uint256 s1,
uint256 r,
uint256 validUntil,
bytes32 evidenceHash,
bytes32 merkleRoot,
uint256 minR,
uint256 maxS1,
bool m0Ok,
bool m1Ok,
bool attestationStale,
bool policyOk,
bool operational,
bool reportingCompliant
)
{
(s0, s1) = _loadSupplies(lineId);
ReserveCommitmentStore.Commitment memory c = reserves.getCommitment(lineId);
r = c.R;
validUntil = c.validUntil;
evidenceHash = c.evidenceHash;
merkleRoot = c.merkleRoot;
minR = PolicyMath.minReservesForM0(s0);
maxS1 = PolicyMath.maxM1ForM0(s0);
attestationStale = _stale(validUntil);
m0Ok = PolicyMath.m0BackingOk(s0, r);
m1Ok = PolicyMath.m1ExpansionOk(s0, s1);
policyOk = PolicyMath.isCompliant(s0, s1, r);
operational = breakers.isLineOperational(lineId);
reportingCompliant = policyOk && !attestationStale && operational;
}
/// @notice Reverts if global/line pause or policy would fail after minting delta M0.
function assertCanMintM0(bytes32 lineId, uint256 mintAmount) external view {
if (!breakers.isLineOperational(lineId)) revert ComplianceBlocked(lineId, "paused");
InstrumentRegistry.Line memory line = registry.getLine(lineId);
if (line.tokenM0 == address(0)) revert UnknownLine(lineId);
if (!line.active) revert ComplianceBlocked(lineId, "inactive");
ReserveCommitmentStore.Commitment memory c = reserves.getCommitment(lineId);
if (breakers.enforceStaleBlockM0() && _stale(c.validUntil)) {
revert ComplianceBlocked(lineId, "stale_attestation");
}
uint256 s0 = IERC20(line.tokenM0).totalSupply();
if (!PolicyMath.canMintM0(s0, mintAmount, c.R)) {
revert ComplianceBlocked(lineId, "m0_backing");
}
}
/// @notice Reverts if global/line pause or M1 cap exceeded after mint.
function assertCanMintM1(bytes32 lineId, uint256 mintAmount) external view {
if (!breakers.isLineOperational(lineId)) revert ComplianceBlocked(lineId, "paused");
InstrumentRegistry.Line memory line = registry.getLine(lineId);
if (line.tokenM0 == address(0)) revert UnknownLine(lineId);
if (!line.active) revert ComplianceBlocked(lineId, "inactive");
ReserveCommitmentStore.Commitment memory c = reserves.getCommitment(lineId);
if (breakers.enforceStaleBlockM1() && _stale(c.validUntil)) {
revert ComplianceBlocked(lineId, "stale_attestation");
}
uint256 s0 = IERC20(line.tokenM0).totalSupply();
uint256 s1 = IERC20(line.tokenM1).totalSupply();
if (!PolicyMath.canMintM1(s0, s1, mintAmount)) {
revert ComplianceBlocked(lineId, "m1_cap");
}
}
/// @notice Permissionless heartbeat for indexers — emits current snapshot (costs gas for caller).
function emitComplianceSnapshot(bytes32 lineId) external {
InstrumentRegistry.Line memory line = registry.getLine(lineId);
if (line.tokenM0 == address(0)) revert UnknownLine(lineId);
uint256 s0 = IERC20(line.tokenM0).totalSupply();
uint256 s1 = IERC20(line.tokenM1).totalSupply();
ReserveCommitmentStore.Commitment memory c = reserves.getCommitment(lineId);
bool stale_ = _stale(c.validUntil);
bool m0Ok = PolicyMath.m0BackingOk(s0, c.R);
bool m1Ok = PolicyMath.m1ExpansionOk(s0, s1);
bool policyOk = PolicyMath.isCompliant(s0, s1, c.R);
bool operational = breakers.isLineOperational(lineId);
bool reporting = policyOk && !stale_ && operational;
emit ComplianceSnapshot(
lineId,
s0,
s1,
c.R,
c.validUntil,
c.evidenceHash,
c.merkleRoot,
m0Ok,
m1Ok,
stale_,
policyOk,
reporting
);
}
}

View File

@@ -0,0 +1,92 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import {AccessControl} from "@openzeppelin/contracts/access/AccessControl.sol";
/**
* @title InstrumentRegistry
* @notice Maps lineId → M0/M1 ERC-20s, ISO-4217 metadata, and pause per line.
*/
contract InstrumentRegistry is AccessControl {
bytes32 public constant REGISTRY_ADMIN_ROLE = keccak256("REGISTRY_ADMIN_ROLE");
struct Line {
address tokenM0;
address tokenM1;
uint8 decimals;
uint16 iso4217Numeric;
bool isXAU;
bool active;
}
mapping(bytes32 lineId => Line) private _lines;
bytes32[] private _allLineIds;
event LineRegistered(
bytes32 indexed lineId,
address tokenM0,
address tokenM1,
uint8 decimals,
uint16 iso4217Numeric,
bool isXAU
);
event LineUpdated(bytes32 indexed lineId, bool active);
event LineTokensUpdated(bytes32 indexed lineId, address tokenM0, address tokenM1);
constructor(address admin) {
require(admin != address(0), "InstrumentRegistry: zero admin");
_grantRole(DEFAULT_ADMIN_ROLE, admin);
_grantRole(REGISTRY_ADMIN_ROLE, admin);
}
function registerLine(
bytes32 lineId,
address tokenM0,
address tokenM1,
uint8 decimals_,
uint16 iso4217Numeric,
bool isXAU
) external onlyRole(REGISTRY_ADMIN_ROLE) {
require(lineId != bytes32(0), "InstrumentRegistry: zero lineId");
require(tokenM0 != address(0) && tokenM1 != address(0), "InstrumentRegistry: zero token");
require(_lines[lineId].tokenM0 == address(0), "InstrumentRegistry: exists");
_lines[lineId] = Line({
tokenM0: tokenM0,
tokenM1: tokenM1,
decimals: decimals_,
iso4217Numeric: iso4217Numeric,
isXAU: isXAU,
active: true
});
_allLineIds.push(lineId);
emit LineRegistered(lineId, tokenM0, tokenM1, decimals_, iso4217Numeric, isXAU);
}
/// @notice All registered line ids (order of registration).
function allLineIds() external view returns (bytes32[] memory) {
return _allLineIds;
}
function setLineActive(bytes32 lineId, bool active) external onlyRole(REGISTRY_ADMIN_ROLE) {
require(_lines[lineId].tokenM0 != address(0), "InstrumentRegistry: unknown line");
_lines[lineId].active = active;
emit LineUpdated(lineId, active);
}
function setLineTokens(bytes32 lineId, address tokenM0, address tokenM1)
external
onlyRole(REGISTRY_ADMIN_ROLE)
{
require(_lines[lineId].tokenM0 != address(0), "InstrumentRegistry: unknown line");
require(tokenM0 != address(0) && tokenM1 != address(0), "InstrumentRegistry: zero token");
_lines[lineId].tokenM0 = tokenM0;
_lines[lineId].tokenM1 = tokenM1;
emit LineTokensUpdated(lineId, tokenM0, tokenM1);
}
function getLine(bytes32 lineId) external view returns (Line memory) {
return _lines[lineId];
}
}

View File

@@ -0,0 +1,53 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import {AccessControl} from "@openzeppelin/contracts/access/AccessControl.sol";
/**
* @title OMNLCircuitBreaker
* @notice Global / per-line pause; optional block on stale attestation for M1 mint path.
*/
contract OMNLCircuitBreaker is AccessControl {
bytes32 public constant GUARDIAN_ROLE = keccak256("GUARDIAN_ROLE");
bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE");
bool public globalPaused;
mapping(bytes32 lineId => bool) public linePaused;
/// @notice If true, M1 mint must revert when attestation is past validUntil (checked in ComplianceCore).
bool public enforceStaleBlockM1;
/// @notice If true, M0 mint must revert when attestation is stale.
bool public enforceStaleBlockM0;
event GlobalPauseSet(bool paused, address indexed by);
event LinePauseSet(bytes32 indexed lineId, bool paused, address indexed by);
event StaleEnforcementSet(bool blockM0, bool blockM1);
constructor(address admin) {
require(admin != address(0), "OMNLCircuitBreaker: zero admin");
_grantRole(DEFAULT_ADMIN_ROLE, admin);
_grantRole(GUARDIAN_ROLE, admin);
_grantRole(PAUSER_ROLE, admin);
}
function setGlobalPaused(bool paused) external onlyRole(GUARDIAN_ROLE) {
globalPaused = paused;
emit GlobalPauseSet(paused, msg.sender);
}
function setLinePaused(bytes32 lineId, bool paused) external onlyRole(PAUSER_ROLE) {
linePaused[lineId] = paused;
emit LinePauseSet(lineId, paused, msg.sender);
}
function setStaleEnforcement(bool blockM0, bool blockM1) external onlyRole(DEFAULT_ADMIN_ROLE) {
enforceStaleBlockM0 = blockM0;
enforceStaleBlockM1 = blockM1;
emit StaleEnforcementSet(blockM0, blockM1);
}
function isLineOperational(bytes32 lineId) external view returns (bool) {
if (globalPaused) return false;
if (linePaused[lineId]) return false;
return true;
}
}

View File

@@ -0,0 +1,109 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {IRouterClient} from "../ccip/IRouterClient.sol";
import {ReserveCommitmentStore} from "./ReserveCommitmentStore.sol";
/**
* @title OMNLMirrorCoordinator
* @notice Primary-chain helper: commits to ReserveCommitmentStore then sends data-only CCIP to mirror receiver.
* @dev Grant RESERVE_COMMITTER_ROLE to this contract on ReserveCommitmentStore.
* Native fee: send `msg.value >= fee`. ERC-20 fee (e.g. LINK): approve this contract for `fee` before calling.
*/
contract OMNLMirrorCoordinator {
using SafeERC20 for IERC20;
IRouterClient public immutable ccipRouter;
ReserveCommitmentStore public immutable reserveStore;
address public admin;
uint64 public mirrorChainSelector;
address public mirrorReceiver;
address public feeToken;
event MirrorChainConfigured(uint64 indexed selector, address indexed receiver, address feeToken);
event MirrorMessageSent(bytes32 indexed messageId, uint64 indexed destSelector, bytes32 indexed lineId, uint256 version);
error OnlyAdmin();
modifier onlyAdmin() {
if (msg.sender != admin) revert OnlyAdmin();
_;
}
constructor(address router_, address reserveStore_, address admin_) {
require(router_ != address(0) && reserveStore_ != address(0) && admin_ != address(0), "OMNLMirrorCoordinator: zero");
ccipRouter = IRouterClient(router_);
reserveStore = ReserveCommitmentStore(reserveStore_);
admin = admin_;
}
function setMirrorDestination(uint64 selector, address receiver, address feeToken_) external onlyAdmin {
mirrorChainSelector = selector;
mirrorReceiver = receiver;
feeToken = feeToken_;
emit MirrorChainConfigured(selector, receiver, feeToken_);
}
function transferAdmin(address next) external onlyAdmin {
require(next != address(0), "OMNLMirrorCoordinator: zero");
admin = next;
}
/// @notice Commit on primary then mirror state via CCIP (data-only message).
function commitReserveAndMirror(
bytes32 lineId,
uint256 R,
uint256 validUntil,
bytes32 evidenceHash,
bytes32 merkleRoot
) external payable returns (bytes32 messageId) {
require(mirrorReceiver != address(0) && mirrorChainSelector != 0, "OMNLMirrorCoordinator: mirror unset");
reserveStore.commitReserve(lineId, R, validUntil, evidenceHash, merkleRoot);
ReserveCommitmentStore.Commitment memory c = reserveStore.getCommitment(lineId);
bytes memory data = abi.encode(c.version, lineId, R, validUntil, evidenceHash, merkleRoot);
IRouterClient.EVM2AnyMessage memory message = IRouterClient.EVM2AnyMessage({
receiver: abi.encode(mirrorReceiver),
data: data,
tokenAmounts: new IRouterClient.TokenAmount[](0),
feeToken: feeToken,
extraArgs: ""
});
uint256 fee = ccipRouter.getFee(mirrorChainSelector, message);
if (feeToken == address(0)) {
require(msg.value >= fee, "OMNLMirrorCoordinator: fee");
(messageId,) = ccipRouter.ccipSend{value: fee}(mirrorChainSelector, message);
} else {
IERC20 ft = IERC20(feeToken);
ft.safeTransferFrom(msg.sender, address(this), fee);
SafeERC20.safeIncreaseAllowance(ft, address(ccipRouter), fee);
(messageId,) = ccipRouter.ccipSend(mirrorChainSelector, message);
}
emit MirrorMessageSent(messageId, mirrorChainSelector, lineId, c.version);
}
function quoteMirrorFee(
bytes32 lineId,
uint256 R,
uint256 validUntil,
bytes32 evidenceHash,
bytes32 merkleRoot
) external view returns (uint256 fee) {
ReserveCommitmentStore.Commitment memory c = reserveStore.getCommitment(lineId);
uint256 nextVersion = c.version + 1;
bytes memory data = abi.encode(nextVersion, lineId, R, validUntil, evidenceHash, merkleRoot);
IRouterClient.EVM2AnyMessage memory message = IRouterClient.EVM2AnyMessage({
receiver: abi.encode(mirrorReceiver),
data: data,
tokenAmounts: new IRouterClient.TokenAmount[](0),
feeToken: feeToken,
extraArgs: ""
});
return ccipRouter.getFee(mirrorChainSelector, message);
}
}

View File

@@ -0,0 +1,80 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import {IRouterClient} from "../ccip/IRouterClient.sol";
import {ReserveCommitmentStore} from "./ReserveCommitmentStore.sol";
/**
* @title OMNLMirrorReceiver
* @notice CCIP receiver on the mirror chain (e.g. 651940) applying mirrored reserve commits from primary (e.g. 138).
* @dev Router must be the canonical CCIP router; allowlist source selectors to prevent spoofed lanes.
*/
contract OMNLMirrorReceiver {
IRouterClient public immutable router;
ReserveCommitmentStore public immutable reserveStore;
address public admin;
mapping(bytes32 => bool) public processedMessages;
mapping(uint64 => bool) public allowedSourceSelectors;
event MirrorCommitReceived(
bytes32 indexed messageId,
uint64 indexed sourceChainSelector,
bytes32 indexed lineId,
uint256 version,
uint256 R,
bytes32 merkleRoot
);
event SourceSelectorSet(uint64 indexed selector, bool allowed);
event AdminTransferred(address indexed previous, address indexed next);
error OnlyRouter();
error OnlyAdmin();
error AlreadyProcessed();
error SourceNotAllowed();
modifier onlyRouter() {
if (msg.sender != address(router)) revert OnlyRouter();
_;
}
modifier onlyAdmin() {
if (msg.sender != admin) revert OnlyAdmin();
_;
}
constructor(address router_, address reserveStore_, address admin_) {
require(router_ != address(0) && reserveStore_ != address(0) && admin_ != address(0), "OMNLMirrorReceiver: zero");
router = IRouterClient(router_);
reserveStore = ReserveCommitmentStore(reserveStore_);
admin = admin_;
}
function setSourceAllowed(uint64 sourceChainSelector, bool allowed) external onlyAdmin {
allowedSourceSelectors[sourceChainSelector] = allowed;
emit SourceSelectorSet(sourceChainSelector, allowed);
}
function transferAdmin(address next) external onlyAdmin {
require(next != address(0), "OMNLMirrorReceiver: zero");
emit AdminTransferred(admin, next);
admin = next;
}
/**
* @notice CCIP entrypoint — payload abi.encode(version, lineId, R, validUntil, evidenceHash, merkleRoot)
*/
function ccipReceive(IRouterClient.Any2EVMMessage calldata message) external onlyRouter {
if (processedMessages[message.messageId]) revert AlreadyProcessed();
if (!allowedSourceSelectors[message.sourceChainSelector]) revert SourceNotAllowed();
processedMessages[message.messageId] = true;
(uint256 version, bytes32 lineId, uint256 R, uint256 validUntil, bytes32 evidenceHash, bytes32 merkleRoot) =
abi.decode(message.data, (uint256, bytes32, uint256, uint256, bytes32, bytes32));
reserveStore.applyMirrorCommit(lineId, R, validUntil, evidenceHash, merkleRoot, version);
emit MirrorCommitReceived(message.messageId, message.sourceChainSelector, lineId, version, R, merkleRoot);
}
}

View File

@@ -0,0 +1,52 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import {Math} from "@openzeppelin/contracts/utils/math/Math.sol";
/**
* @title PolicyMath
* @notice Deterministic GRU M0/M1 rules: 1.2× reserves on M0; ≤5× M1 vs M0 per line.
* @dev Must match docs/hybx-omnl/HYBX_OMNL_POLICY_SPEC.md
*/
library PolicyMath {
uint256 internal constant M0_NUM = 12;
uint256 internal constant M0_DEN = 10;
uint256 internal constant M1_CAP_NUM = 5;
uint256 internal constant M1_CAP_DEN = 1;
/// @notice Minimum reserves R required to back S0 units of M0 (ceil(1.2 * S0)).
function minReservesForM0(uint256 s0) internal pure returns (uint256) {
return Math.mulDiv(s0, M0_NUM, M0_DEN, Math.Rounding.Ceil);
}
/// @notice Maximum M1 supply allowed for S0 units of M0 (5 * S0).
function maxM1ForM0(uint256 s0) internal pure returns (uint256) {
return Math.mulDiv(s0, M1_CAP_NUM, M1_CAP_DEN);
}
function m0BackingOk(uint256 s0, uint256 r) internal pure returns (bool) {
return r >= minReservesForM0(s0);
}
function m1ExpansionOk(uint256 s0, uint256 s1) internal pure returns (bool) {
return s1 <= maxM1ForM0(s0);
}
function isCompliant(uint256 s0, uint256 s1, uint256 r) internal pure returns (bool) {
return m0BackingOk(s0, r) && m1ExpansionOk(s0, s1);
}
function canMintM0(uint256 s0Before, uint256 mintAmount, uint256 r) internal pure returns (bool) {
unchecked {
uint256 s0After = s0Before + mintAmount;
return m0BackingOk(s0After, r);
}
}
function canMintM1(uint256 s0, uint256 s1Before, uint256 mintAmount) internal pure returns (bool) {
unchecked {
uint256 s1After = s1Before + mintAmount;
return m1ExpansionOk(s0, s1After);
}
}
}

View File

@@ -0,0 +1,175 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import {AccessControl} from "@openzeppelin/contracts/access/AccessControl.sol";
import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
/**
* @title ReserveCommitmentStore
* @notice Stores attested reserve R per line with TTL, merkle evidence root, versioning, primary + mirror,
* and optional threshold ECDSA attestation for commits.
*/
contract ReserveCommitmentStore is AccessControl {
bytes32 public constant RESERVE_COMMITTER_ROLE = keccak256("RESERVE_COMMITTER_ROLE");
bytes32 public constant ATTESTATION_ADMIN_ROLE = keccak256("ATTESTATION_ADMIN_ROLE");
/// @notice EIP-191 structured preimage prefix for attested commits (see commitReserveAttested).
bytes32 public constant ATTESTATION_TYPEHASH =
keccak256("OMNLReserveCommit(uint256 chainId,address store,bytes32 lineId,uint256 R,uint256 validUntil,bytes32 evidenceHash,bytes32 merkleRoot,uint256 nonce)");
struct Commitment {
uint256 R;
uint256 validUntil;
bytes32 evidenceHash;
bytes32 merkleRoot;
uint256 version;
}
address public mirrorReceiver;
mapping(bytes32 lineId => Commitment) private _commitments;
mapping(bytes32 lineId => uint256) public lineAttestationNonce;
mapping(address => bool) public isAttestationSigner;
uint256 public attestationThreshold;
event ReserveCommitted(
bytes32 indexed lineId,
uint256 R,
uint256 validUntil,
bytes32 evidenceHash,
bytes32 merkleRoot,
uint256 version,
address indexed by
);
event MirrorReceiverUpdated(address indexed oldReceiver, address indexed newReceiver);
event AttestationSignersUpdated(uint256 threshold);
event AttestationSignerSet(address indexed signer, bool active);
constructor(address admin) {
require(admin != address(0), "ReserveCommitmentStore: zero admin");
_grantRole(DEFAULT_ADMIN_ROLE, admin);
_grantRole(RESERVE_COMMITTER_ROLE, admin);
_grantRole(ATTESTATION_ADMIN_ROLE, admin);
}
function setMirrorReceiver(address receiver) external onlyRole(DEFAULT_ADMIN_ROLE) {
address old = mirrorReceiver;
mirrorReceiver = receiver;
emit MirrorReceiverUpdated(old, receiver);
}
function setAttestationSigner(address signer, bool active) external onlyRole(ATTESTATION_ADMIN_ROLE) {
isAttestationSigner[signer] = active;
emit AttestationSignerSet(signer, active);
}
function setAttestationThreshold(uint256 threshold) external onlyRole(ATTESTATION_ADMIN_ROLE) {
attestationThreshold = threshold;
emit AttestationSignersUpdated(threshold);
}
/// @notice Primary chain (or designated committer) updates reserves.
function commitReserve(
bytes32 lineId,
uint256 R,
uint256 validUntil,
bytes32 evidenceHash,
bytes32 merkleRoot
) external onlyRole(RESERVE_COMMITTER_ROLE) {
_commit(lineId, R, validUntil, evidenceHash, merkleRoot, msg.sender);
}
/// @notice Threshold ECDSA attestation (any EOA may submit once enough distinct signers included).
function commitReserveAttested(
bytes32 lineId,
uint256 R,
uint256 validUntil,
bytes32 evidenceHash,
bytes32 merkleRoot,
uint256 nonce,
bytes[] calldata signatures
) external {
require(attestationThreshold > 0, "ReserveCommitmentStore: attestation off");
require(nonce == lineAttestationNonce[lineId], "ReserveCommitmentStore: nonce");
bytes32 digest = keccak256(
abi.encode(
ATTESTATION_TYPEHASH,
block.chainid,
address(this),
lineId,
R,
validUntil,
evidenceHash,
merkleRoot,
nonce
)
);
bytes32 ethSignedMessageHash = keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", digest));
uint256 n = signatures.length;
require(n >= attestationThreshold, "ReserveCommitmentStore: sig count");
address[] memory seen = new address[](n);
uint256 uniq = 0;
for (uint256 i = 0; i < n; i++) {
address recovered = ECDSA.recover(ethSignedMessageHash, signatures[i]);
require(isAttestationSigner[recovered], "ReserveCommitmentStore: not signer");
for (uint256 j = 0; j < uniq; j++) {
require(seen[j] != recovered, "ReserveCommitmentStore: duplicate signer");
}
seen[uniq] = recovered;
unchecked {
++uniq;
}
}
require(uniq >= attestationThreshold, "ReserveCommitmentStore: threshold");
lineAttestationNonce[lineId] = nonce + 1;
_commit(lineId, R, validUntil, evidenceHash, merkleRoot, msg.sender);
}
function _commit(
bytes32 lineId,
uint256 R,
uint256 validUntil,
bytes32 evidenceHash,
bytes32 merkleRoot,
address by
) internal {
Commitment storage c = _commitments[lineId];
unchecked {
c.version += 1;
}
c.R = R;
c.validUntil = validUntil;
c.evidenceHash = evidenceHash;
c.merkleRoot = merkleRoot;
emit ReserveCommitted(lineId, R, validUntil, evidenceHash, merkleRoot, c.version, by);
}
/// @notice Applied by OMNLMirrorReceiver after CCIP validation (monotonic version).
function applyMirrorCommit(
bytes32 lineId,
uint256 R,
uint256 validUntil,
bytes32 evidenceHash,
bytes32 merkleRoot,
uint256 version
) external {
require(msg.sender == mirrorReceiver, "ReserveCommitmentStore: only mirror");
Commitment storage c = _commitments[lineId];
require(version > c.version, "ReserveCommitmentStore: version");
c.version = version;
c.R = R;
c.validUntil = validUntil;
c.evidenceHash = evidenceHash;
c.merkleRoot = merkleRoot;
emit ReserveCommitted(lineId, R, validUntil, evidenceHash, merkleRoot, version, msg.sender);
}
function getCommitment(bytes32 lineId) external view returns (Commitment memory) {
return _commitments[lineId];
}
}

View File

@@ -0,0 +1,12 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
/**
* @title IZkReserveProofVerifier
* @notice Optional hook for zk-SNARK verification that R >= f(S0) with private bank balances.
* @dev Deploy a verifier implementation and call from an extended ReserveCommitmentStore or wrapper.
*/
interface IZkReserveProofVerifier {
/// @notice Returns true if the proof is valid for the given public inputs (e.g. commitment_R, lineId hash).
function verifyProof(bytes calldata proof, uint256[] calldata publicInputs) external view returns (bool valid);
}

View File

@@ -0,0 +1,64 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import {AccessControl} from "@openzeppelin/contracts/access/AccessControl.sol";
/**
* @title PolicyProfileRegistry
* @notice Minimal on-chain anchor for URA `policyProfileId` rows (content hash + version + effective time).
* Pair with off-chain `config/universal-resource-activation/policy-profiles.json` and
* `scripts/ura/policy-profiles-content-hash.mjs` in the proxmox repo. Not a full GRU M00 diamond.
*/
contract PolicyProfileRegistry is AccessControl {
bytes32 public constant PUBLISHER_ROLE = keccak256("PUBLISHER_ROLE");
struct Record {
bytes32 contentHash;
uint64 version;
uint256 effectiveFrom;
bool exists;
}
mapping(bytes32 => Record) private _records;
event ProfilePublished(
bytes32 indexed profileKey,
string policyProfileId,
bytes32 contentHash,
uint64 version,
uint256 effectiveFrom
);
constructor(address admin) {
require(admin != address(0), "PolicyProfileRegistry: admin");
_grantRole(DEFAULT_ADMIN_ROLE, admin);
_grantRole(PUBLISHER_ROLE, admin);
}
function profileKey(string calldata policyProfileId) public pure returns (bytes32) {
return keccak256(bytes(policyProfileId));
}
/**
* @param contentHash keccak256 of canonical JSON for this profile row (see proxmox hash script)
*/
function publishProfile(
string calldata policyProfileId,
bytes32 contentHash,
uint64 version,
uint256 effectiveFrom
) external onlyRole(PUBLISHER_ROLE) {
bytes32 key = profileKey(policyProfileId);
_records[key] = Record({
contentHash: contentHash,
version: version,
effectiveFrom: effectiveFrom,
exists: true
});
emit ProfilePublished(key, policyProfileId, contentHash, version, effectiveFrom);
}
function getRecord(string calldata policyProfileId) external view returns (Record memory) {
return _records[profileKey(policyProfileId)];
}
}

View File

@@ -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`
---

View File

@@ -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

View File

@@ -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

View File

@@ -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 contracts `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).

View File

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

View File

@@ -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!

View File

@@ -0,0 +1,41 @@
# CCIP mirror flow — Chain 138 ↔ ALL Mainnet (651940)
## Roles
- **Primary reserve writer:** Chain **138** (`ReserveCommitmentStore.commitReserve` or `commitReserveAttested`) after operational attestation signing.
- **One-shot mirror send:** `OMNLMirrorCoordinator.commitReserveAndMirror` on **138** commits then sends CCIP (native fee). Grant `RESERVE_COMMITTER_ROLE` on `ReserveCommitmentStore` to the coordinator address.
- **Mirror chain:** **651940** receives identical commitments via `OMNLMirrorReceiver.ccipReceive`, which calls `ReserveCommitmentStore.applyMirrorCommit` (caller must equal `mirrorReceiver` set on the store).
## Finality
- **138 (QBFT):** treat blocks as final immediately for attestation pipeline (`confirmations: 1` in indexer config).
- **651940:** wait **12 confirmations** before treating mirrored state as binding for **breaker automation** that affects user funds (matches [chains.ts](../../services/token-aggregation/src/config/chains.ts) defaults).
## Payload encoding
Off-chain helper (repo): `services/token-aggregation/scripts/encode-omnl-mirror-payload.mjs`.
`OMNLMirrorReceiver` decodes `message.data` as:
```text
abi.encode(version, lineId, R, validUntil, evidenceHash, merkleRoot)
```
- `version` must be **strictly greater** than the stored version for `lineId` on the mirror store.
- `lineId` is `bytes32`.
- `R`, `validUntil`, `evidenceHash`, `merkleRoot` must match the primary commit being mirrored.
## Router integration
1. Deploy `OMNLMirrorReceiver(router651940, reserveStore651940, admin)` on ALL Mainnet.
2. On `ReserveCommitmentStore` (651940): `setMirrorReceiver(receiverAddr)` (admin).
3. On receiver: `setSourceAllowed(chainSelector138, true)` with the **Chainlink CCIP selector** for Chain 138 as **source**.
4. From Chain 138, send CCIP messages to `receiver` on 651940 with **no token amounts** (data-only message) using the encoded payload above. Fee in native or LINK per router config.
## Replay protection
`OMNLMirrorReceiver` marks `messageId` processed (same pattern as [CCIPRelayBridge](../../contracts/relay/CCIPRelayBridge.sol)).
## Desync breaker
If mirrored `version` / `R` on 651940 lags primary beyond `N` blocks (operator config), automation documented in [RUNBOOK_CIRCUIT_BREAKERS.md](./RUNBOOK_CIRCUIT_BREAKERS.md) should alert and optionally invoke `OMNLCircuitBreaker.setLinePaused` / `setGlobalPaused`.

View File

@@ -0,0 +1,24 @@
# HYBX OMNL — deployment checklist
Use with `config/deployment-omnl.example.env` (copy to `.env` on the host; never commit secrets).
## Pre-flight
1. **Contracts**: `forge script script/hybx-omnl/DeployOMNLStack.s.sol` (or `DeployMirrorCoordinator.s.sol` for coordinator-only) on target chains; record addresses.
2. **CCIP**: Router and LINK (or native fee) match `hybx-omnl-cross-chain-lines.json`; coordinator `approve` path for ERC-20 fee token if used.
3. **Policy**: `hybx-omnl-policy.json` matches on-chain `PolicyMath` / registry limits (1.2× M0, 5× M1).
4. **Fineract**: GL accounts exist; pagination env `OMNL_FINERACT_GL_PAGE_LIMIT` set if >200 accounts.
## Token aggregation service
1. Set `OMNL_*`, `ENABLE_OMNL_EVENT_POLLER`, `OMNL_POLLER_STATE_PATH` (optional), webhook URLs/secrets, optional `OMNL_API_KEY` / `OMNL_DASHBOARD_TOKEN`, `OMNL_RATE_LIMIT_*` as needed.
2. `pnpm run build` in `services/token-aggregation`.
3. Health: `GET /health`; OMNL: `GET /api/v1/omnl/compliance`; dashboard: `GET /omnl/dashboard`.
4. Run `bash scripts/hybx-omnl/verify-deployment.sh` (Forge `hybx-omnl` tests + `tsc` in `services/token-aggregation`).
5. **Publish snapshot** (`smom-dbis-138-publish/`): run the same script name there for TypeScript + `omnl-reconcile-report.mjs` only; Forge deps live in the main repo.
## Post-deploy
1. Anchor reconciliation hash: `pnpm run omnl:reconcile:artifact` (repo root) or `bash scripts/hybx-omnl/omnl-reconcile-artifact.sh` (writes `artifacts/omnl-reconcile/omnl-reconcile-sha256.txt`) or `pnpm run omnl:reconcile` under `services/token-aggregation` / `node services/token-aggregation/scripts/omnl-reconcile-report.mjs` → store `sha256` in ops log. Cron and GitHub Actions: see `docs/hybx-omnl/OMNL_RECONCILE_CRON_AND_CI.md`.
2. Mirror smoke: send test payload per [CCIP_MIRROR_FLOW.md](CCIP_MIRROR_FLOW.md) (staging first).
3. Operational compliance: see `OPERATIONAL_COMPLIANCE.md` (retention, webhook signing, break-glass).

View File

@@ -0,0 +1,31 @@
# HYBX OMNL — external audit checklist
Use this with a third-party firm before high-value production. Scope aligns with [SECURITY_THREAT_MODEL.md](SECURITY_THREAT_MODEL.md).
## Solidity (in scope)
- [ ] `PolicyMath.sol` — rounding, overflow, parameter bounds vs documented policy.
- [ ] `InstrumentRegistry.sol` — role changes, line lifecycle, token registration assumptions.
- [ ] `ReserveCommitmentStore.sol``commitReserve` / `commitReserveAttested`, ECDSA digest, replay, threshold logic.
- [ ] `ComplianceCore.sol``getCompliance` semantics vs `PolicyMath`, stale attestation, `reportingCompliant`.
- [ ] `OMNLCircuitBreaker.sol` — pause semantics, admin roles.
- [ ] `OMNLMirrorReceiver.sol` — CCIP payload decoding, selector allowlist, monotonic version.
- [ ] `OMNLMirrorCoordinator.sol` — native vs ERC-20 fee path, `approve`/`SafeERC20`, reentrancy surface (minimal).
## Operational evidence to provide auditors
- [ ] Deployed addresses per chain (138 / 651940) and verification on block explorers.
- [ ] Key ceremony summary (HSM / multisig); no plaintext prod keys in CI.
- [ ] CCIP lane configuration (router, selectors, fee token).
- [ ] Sample `ReserveCommitted` and mirror receive transactions on testnet/staging.
## Off-chain (optional scope)
- [ ] Token-aggregation OMNL routes — rate limits, `OMNL_API_KEY` usage, webhook HMAC verification at receivers.
- [ ] IPSAS registry / journal matrix change control (who can commit, how hash is anchored).
## Sign-off
| Finding | Severity | Remediation | Retest date |
|---------|----------|-------------|-------------|
| | | | |

View File

@@ -0,0 +1,54 @@
# HYBX OMNL — frozen policy specification
This document locks **deterministic** monetary rules for GRU v2 (ISO-4217 lines) and **cXAU\*** (troy ounce) instruments. On-chain code in `contracts/hybx-omnl/` must match this spec.
## Scope
- **Per-line aggregates:** Each `lineId` (e.g. USD-stable line, EUR line, XAU line) has independent `S0`, `S1`, `R`. The **5:1 M1 cap** applies **per line**, not globally across unrelated ISO codes.
- **Units:** All of `S0`, `S1`, `R` for a given `lineId` use the **same minor unit** as the registered M0 token (e.g. 6 decimals for typical c\* stables). For XAU lines, **1.0 token = 1 troy ounce** at full precision (see explorer cross-check); reserves `R` are **troy ounces** in that same atom.
## Rounding (mulDiv)
Let `M0_NUM = 12`, `M0_DEN = 10` (1.2× backing).
- **Minimum reserves** required for current M0 supply `S0`:
`minR(S0) = ceil(S0 * M0_NUM / M0_DEN)` using `Math.mulDiv(S0, M0_NUM, M0_DEN, Rounding.Ceil)`.
Intuition: any fractional atom of M0 requires full ceil so backing never falls below 1.2× in discrete units.
- **M1 cap** for current `S0`:
`maxS1(S0) = S0 * 5` (exact; no rounding down of the cap in favor of more M1).
Compliance:
- **M0 backing:** `R >= minR(S0)`.
- **M1 expansion:** `S1 <= maxS1(S0)`.
**Mint simulation:** Before minting `Δ` M0: require `R >= minR(S0 + Δ)`. Before minting `Δ` M1: require `S1 + Δ <= maxS1(S0)`.
## Instrument registration
- Each **line** registers exactly one **M0** ERC-20 and one **M1** ERC-20 (`InstrumentRegistry`).
- Tokens used for supply reads must be **standard** `ERC20.totalSupply()`; rebasing or fee-on-transfer tokens are **out of scope** unless wrapped to a fixed-supply view.
- **ISO-4217** numeric code is stored for GRU v2 fiat lines; XAU lines set `isXAU = true` and ignore ISO fiat code.
## Attestation vs price oracles
- **Reserve commitments `R`** come only from `ReserveCommitmentStore` (manual multisig / HSM pipeline). They are **not** PMM or CoinGecko prices.
- **Price / PMM oracles** remain separate ([ORACLE_AND_KEEPER_CHAIN138](../integration/ORACLE_AND_KEEPER_CHAIN138.md)).
## Primary chain for writes
- **Default:** Chain **138** is the **primary** committer for `R` and policy parameter updates; ALL Mainnet (**651940**) receives **mirrored** commitments via CCIP (`OMNLMirrorReceiver`). Operators may switch primary only via governance-controlled addresses documented at deploy time.
## Stale attestation (operational default)
- If `now > validUntil` for a lines last commitment, the **API** surfaces `attestationStale: true`. On-chain **pause** of M1 mint is **optional** and controlled by `OMNLCircuitBreaker` TTL — default **warn-only** until `enforceStaleBlockM0/M1` is enabled.
- **`reportingCompliant` (on-chain / API):** `policyOk && !attestationStale && operational` — use this for dashboards when stale attestations must fail the “green” status even if pure math (`policyOk`) still holds.
- **Merkle evidence:** `ReserveCommitmentStore` stores `merkleRoot` alongside `evidenceHash` for periodic reconciliation roots.
## Cross-chain aggregated supply (API)
- For lines with M0/M1 on both 138 and 651940, configure [`config/hybx-omnl-cross-chain-lines.json`](../../config/hybx-omnl-cross-chain-lines.json) and call **`GET /api/v1/omnl/compliance-aggregated/:lineId`**. Reserves `R` / TTL are taken from primary (**138**) `ComplianceCore`; supplies are summed per `totalSupply()` across chains.

View File

@@ -0,0 +1,67 @@
# OMNL IPSAS / GL validation API (token-aggregation)
Endpoints are served under **`/api/v1`** when the token-aggregation service is running. They tie **on-chain OMNL compliance** to **IPSAS-aligned GL codes** documented for OMNL Hybx (Fineract).
**Discovery:** `GET /api/v1/omnl/catalog` — machine-readable list of all OMNL routes, auth notes, and query parameters.
**OpenAPI 3:** `GET /api/v1/omnl/openapi.json` — static OpenAPI 3.0 document for Swagger UI, Postman import, and codegen.
## Configuration files (smom-dbis-138)
| File | Purpose |
|------|---------|
| [`config/omnl-ipsas-gl-registry.json`](../../config/omnl-ipsas-gl-registry.json) | Canonical GL codes (1000, 1050, 2000, 2100, 3000), IPSAS references, allowed pairs, monetary-layer hints |
| [`config/omnl-journal-matrix.json`](../../config/omnl-journal-matrix.json) | T-001…T-008 journal lines |
Override paths with `OMNL_IPSAS_GL_REGISTRY` and `OMNL_JOURNAL_MATRIX_PATH` if needed.
## Fineract (live compare & health)
Set:
- `OMNL_FINERACT_BASE_URL`
- `OMNL_FINERACT_TENANT` (default `omnl`)
- `OMNL_FINERACT_USER` (default `app.omnl`) — or legacy **`OMNL_FINERACT_USERNAME`**
- `OMNL_FINERACT_PASSWORD`
- `OMNL_FINERACT_GL_PAGE_LIMIT` (pagination for large tenants)
| Method | Path | Description |
|--------|------|-------------|
| GET | `/omnl/ipsas/fineract-health` | Probe `/glaccounts?limit=1` — returns `ok`, `statusCode`, `configured` |
| GET | `/omnl/ipsas/fineract-compare` | Full GL list vs registry (requires **`OMNL_API_KEY`** when that env is set) |
## IPSAS validation
| Method | Path | Description |
|--------|------|-------------|
| GET | `/omnl/ipsas/registry` | Full IPSAS GL registry JSON |
| GET | `/omnl/ipsas/matrix` | Journal matrix entries |
| GET | `/omnl/ipsas/validate-pair?debitGlCode=&creditGlCode=` | Validate one pair (registry + matrix) |
| POST | `/omnl/ipsas/validate-pairs` | Body: `{ "pairs": [{ "debitGlCode", "creditGlCode" }, ...] }` — batch validation |
| GET | `/omnl/ipsas/layer/:layer` | `m0_reserve` \| `m1_liability` \| `settlement` \| `equity` |
| GET | `/omnl/ipsas/compliance-context/:lineId?aggregated=1` | Compliance snapshot + IPSAS guidance (**`OMNL_API_KEY`** when set) |
## Cross-cutting OMNL APIs (same `/api/v1` prefix)
| Method | Path | Description |
|--------|------|-------------|
| GET | `/omnl/reconcile-anchor` | SHA-256 of canonical IPSAS registry + journal matrix (same as `omnl-reconcile-report.mjs`) |
| GET | `/omnl/integration-status` | Booleans for configured env groups (no secrets) |
| GET | `/omnl/cross-chain-lines` | `hybx-omnl-cross-chain-lines.json` content + path |
| GET | `/omnl/zk-verifier` | Address from `OMNL_ZK_VERIFIER` |
| GET | `/omnl/mirror-coordinator?chainId=138` | On-chain `mirrorChainSelector`, `mirrorReceiver`, `feeToken` |
## Example
```http
GET /api/v1/omnl/ipsas/validate-pair?debitGlCode=1050&creditGlCode=2000
```
```http
POST /api/v1/omnl/ipsas/validate-pairs
Content-Type: application/json
{"pairs":[{"debitGlCode":"1050","creditGlCode":"2000"}]}
```
Expect `ipsasCompliantPair: true` when the pair is allowed in the registry or matrix.

View File

@@ -0,0 +1,55 @@
# OMNL reconcile — cron and CI
The anchor is the SHA-256 of canonical JSON built from `config/omnl-ipsas-gl-registry.json` and `config/omnl-journal-matrix.json` (see `services/token-aggregation/scripts/omnl-reconcile-report.mjs`).
## One-off (repo root)
```bash
bash scripts/hybx-omnl/omnl-reconcile-artifact.sh
```
Outputs under `artifacts/omnl-reconcile/` (gitignored locally):
| File | Purpose |
|------|---------|
| `omnl-reconcile-<UTC-timestamp>.json` | Immutable run record |
| `omnl-reconcile-latest.json` | Copy of last run |
| `omnl-reconcile-sha256.txt` | Single line: hex digest for scripts / alerting |
| `omnl-reconcile-ci-meta.json` | Present when `GITHUB_*` env vars are set |
Override output directory: `OMNL_RECONCILE_ARTIFACT_DIR=/var/lib/omnl bash scripts/hybx-omnl/omnl-reconcile-artifact.sh`
## Cron example
Run daily at 06:00 UTC. The script calls `node` on `omnl-reconcile-report.mjs` (no `npm install` required; only Node.js and repo files).
```cron
# OMNL IPSAS / journal matrix anchor (adjust paths and log location)
0 6 * * * cd /opt/smom-dbis-138 && /usr/bin/bash scripts/hybx-omnl/omnl-reconcile-artifact.sh >> /var/log/omnl-reconcile.log 2>&1
```
After registry or matrix edits in git, re-run the script (or rely on CI) and archive the new `sha256` in your ops log.
## GitHub Actions
### Scheduled / manual anchor
Workflow: `.github/workflows/omnl-reconcile.yml`
- **schedule**: weekly (edit cron as needed)
- **workflow_dispatch**: manual run
- **push** / **pull_request**: when OMNL config or the reconcile script changes
Artifacts: download **omnl-reconcile-\<run id\>** from the Actions run; it contains the same files as above.
### PR checks (TypeScript + anchor, no Forge)
Workflow: `.github/workflows/hybx-omnl-ts.yml`
- **workflow_dispatch**: run manually from the Actions tab (no path filter).
- Runs on **pull_request** and **push** when OMNL contracts, token-aggregation, OMNL config, or `scripts/hybx-omnl/**` change.
- Steps: `npm ci` in `services/token-aggregation`, `omnl-reconcile-artifact.sh`, `npm run build`, uploads **`artifacts/omnl-reconcile/`** (14-day retention).
Full stack validation (Forge `hybx-omnl` + `tsc`): `pnpm run omnl:verify` or `bash scripts/hybx-omnl/ci-omnl-validation.sh` locally (requires Foundry + `lib/`).
Both OMNL workflows use **concurrency** (cancel in-progress on the same ref) to avoid stacked runs on rapid pushes.

View File

@@ -0,0 +1,45 @@
# Operational compliance (OMNL)
## Webhooks
- Configure `OMNL_WEBHOOK_URLS` (comma-separated HTTPS endpoints).
- Set `OMNL_WEBHOOK_SECRET`. Each POST body is **UTF-8 JSON**; **`X-OMNL-Signature`** = `sha256=` + **hex(HMAC-SHA256(secret, rawBody))** (same bytes as the request body). Use `verifyOmnlWebhookSignature()` from `omnl-webhooks.ts` or reimplement with the same algorithm. **Timing-safe** compare the full header value.
- Payloads include **`deliveryId`** (e.g. `138-12345-2`) for idempotent processing at the receiver.
- Prefer allowlists and TLS 1.2+ only; rotate secrets on break-glass.
## API hardening
- **`OMNL_API_KEY`**: when set, `GET /api/v1/omnl/ipsas/fineract-compare` and `.../compliance-context/:lineId` require `Authorization: Bearer <key>` or `?access_token=<key>`.
- **`OMNL_DASHBOARD_TOKEN`**: when set, `GET /omnl/dashboard` requires the same token via `?access_token=` or header `X-OMNL-Dashboard-Token`. For Fineract compare in the embedded page, open **`/omnl/dashboard?access_token=<OMNL_API_KEY>`** so the script can call protected routes.
- **OMNL rate limit**: `OMNL_RATE_LIMIT_MAX` / `OMNL_RATE_LIMIT_WINDOW_MS` (default 30/min per IP on `/api/v1/omnl/*`, in addition to the global API limiter).
## Logs and retention
- Reserve commit and compliance events are emitted to application logs; align retention with your policy (often 90 days minimum for financial audit support).
- **Config anchor (IPSAS / journal matrix):** after registry/matrix JSON changes, run `bash scripts/hybx-omnl/omnl-reconcile-artifact.sh` — this hashes **off-chain config files only**, not custodian bank balances vs on-chain `R`. Use a separate control for **bank ↔ on-chain** reconciliation if required for your auditor.
- Automate anchors via cron or GitHub Actions — [OMNL_RECONCILE_CRON_AND_CI.md](OMNL_RECONCILE_CRON_AND_CI.md).
## Poller state
- **`OMNL_POLLER_STATE_PATH`**: optional path for JSON storing last processed block per chain (default: `.omnl-poller-state.json` in `cwd`). Survives restarts to avoid re-querying large ranges; webhook consumers should still treat **`deliveryId`** as idempotent.
## Recommended alerts (operational)
Wire your log/metrics stack to alert on:
- Webhook POST failures (warn logs from `omnl-webhooks.ts`).
- `reportingCompliant === false` or `attestationStale` from compliance APIs for critical `lineId`s.
- `mirror-status` / `inSync === false` when both reserve stores are configured.
- Repeated `401` on OMNL routes (possible credential scanning).
## Break-glass
- Document who can pause `OMNLCircuitBreaker` and rotate coordinator keys; store procedures outside this repo per org policy.
## Data minimization
- Webhook payloads avoid full PII; line IDs and hashes only unless contractually required.
## External audit
- See [EXTERNAL_AUDIT_CHECKLIST.md](EXTERNAL_AUDIT_CHECKLIST.md).

24
docs/hybx-omnl/README.md Normal file
View File

@@ -0,0 +1,24 @@
# HYBX OMNL documentation
Multi-chain instrument registry, reserve commitments, compliance core, IPSAS-aligned GL reporting, and optional CCIP mirror to ALL Mainnet (651940).
| Document | Purpose |
|----------|---------|
| [HYBX_OMNL_POLICY_SPEC.md](HYBX_OMNL_POLICY_SPEC.md) | On-chain policy math (1.2× M0, 5× M1) and limits |
| [DEPLOYMENT_CHECKLIST.md](DEPLOYMENT_CHECKLIST.md) | Contracts, env, API, verification |
| [CCIP_MIRROR_FLOW.md](CCIP_MIRROR_FLOW.md) | Primary → mirror CCIP payload and roles |
| [OMNL_IPSAS_API.md](OMNL_IPSAS_API.md) | REST routes for compliance + IPSAS / Fineract GL |
| [OMNL_RECONCILE_CRON_AND_CI.md](OMNL_RECONCILE_CRON_AND_CI.md) | Anchor hash: cron, `omnl-reconcile-artifact.sh`, CI artifacts |
| [EXTERNAL_AUDIT_CHECKLIST.md](EXTERNAL_AUDIT_CHECKLIST.md) | Third-party audit scope checklist |
| [OPERATIONAL_COMPLIANCE.md](OPERATIONAL_COMPLIANCE.md) | Webhooks, logs, break-glass, data minimization |
| [RUNBOOK_CIRCUIT_BREAKERS.md](RUNBOOK_CIRCUIT_BREAKERS.md) | Circuit breaker operations |
| [SECURITY_THREAT_MODEL.md](SECURITY_THREAT_MODEL.md) | Threat notes |
| [ZK_INTEGRATION.md](ZK_INTEGRATION.md) | Optional ZK reserve proof verifier hook |
Configuration: `config/hybx-omnl-policy.json`, `config/hybx-omnl-cross-chain-lines.json`, `config/omnl-ipsas-gl-registry.json`, `config/omnl-journal-matrix.json`, `config/deployment-omnl.example.env`.
Contracts: `contracts/hybx-omnl/`. Tests: `pnpm run forge:test:omnl` or `bash scripts/forge/scope.sh test hybx-omnl`. Full check: `pnpm run omnl:verify` (repo root). Anchor files: `pnpm run omnl:reconcile:artifact`.
GitHub Actions: `.github/workflows/hybx-omnl-ts.yml` (TypeScript build + anchor on PR/push), `.github/workflows/omnl-reconcile.yml` (weekly anchor + CI meta); both support **workflow_dispatch**. Publish snapshot `smom-dbis-138-publish/` uses the same workflows; Forge-heavy checks run in the main repo only (`pnpm run omnl:verify`).
Scripts: `bash scripts/hybx-omnl/sync-to-publish.sh` (mirror OMNL paths to publish tree), `node scripts/hybx-omnl/validate-cross-chain-config.mjs` (JSON shape + addresses for `hybx-omnl-cross-chain-lines.json`).

View File

@@ -0,0 +1,30 @@
# HYBX OMNL — circuit breakers and operations
## Controls (on-chain)
| Control | Contract | When to use |
|---------|----------|-------------|
| Global pause | `OMNLCircuitBreaker.setGlobalPaused(true)` | Bridge desync, key compromise affecting multiple lines, emergency |
| Line pause | `OMNLCircuitBreaker.setLinePaused(lineId, true)` | Single-asset stress, custodian dispute |
| Stale enforcement | `OMNLCircuitBreaker.setStaleEnforcement(blockM0, blockM1)` | Turn on hard **revert** on mint when `validUntil` passed (default warn-only via API) |
Roles: `GUARDIAN_ROLE` (global), `PAUSER_ROLE` (line), `DEFAULT_ADMIN_ROLE` (stale policy).
## Signals
1. **TTL monitor**`services/token-aggregation/scripts/omnl-ttl-monitor.mjs` (cron every 15 min). Exit 1 → page on-call.
2. **Cross-chain drift**`GET /api/v1/omnl/mirror-status/:lineId` compares reserve **version** and `r` (requires `OMNL_RESERVE_STORE_138` and `OMNL_RESERVE_STORE_651940`). `GET /api/v1/omnl/health` (needs `OMNL_HEALTH_LINE_ID`) compares compliance snapshots including `reportingCompliant`.
3. **Policy breach** — index `ComplianceSnapshot` events or poll `getCompliance` for `policyOk: false`.
## Runbook: mirror lag
1. Check CCIP message queue / relayer health on primary.
2. Verify `allowedSourceSelectors` on `OMNLMirrorReceiver` (651940).
3. Compare `version` in `ReserveCommitted` events 138 vs 651940.
4. If unrecoverable within SLA: `setGlobalPaused(true)`, investigate, then unpause after replay or manual `commitReserve` alignment.
## Runbook: stale attestation
1. Run custodian pipeline; submit new `commitReserve` on primary with fresh `validUntil`.
2. Mirror to 651940 via CCIP per [CCIP_MIRROR_FLOW.md](./CCIP_MIRROR_FLOW.md).
3. If still stale after SLA: pause M1 mints (`setLinePause` or `setStaleEnforcement`).

View File

@@ -0,0 +1,30 @@
# HYBX OMNL — threat model and audit scope
## Trust boundaries
- **On-chain policy** (`PolicyMath`, `ComplianceCore`) is deterministic given inputs `(S0, S1, R)` from `IERC20.totalSupply` and `ReserveCommitmentStore`.
- **Economic truth of R** depends on **custodian attestation** and operational security of signing keys — not on PMM or spot price oracles.
## Threats and mitigations
| Threat | Impact | Mitigation |
|--------|--------|------------|
| Compromised `RESERVE_COMMITTER` key | Fake reserves | Multisig / HSM, key rotation, monitoring on `ReserveCommitted` |
| Compromised attestation signer set (`commitReserveAttested`) | Forged threshold commits | Rotate `isAttestationSigner`, raise `attestationThreshold`, monitor `lineAttestationNonce` |
| Compromised CCIP lane | Wrong mirror updates | `allowedSourceSelectors`, replay protection on `messageId` |
| Malicious ERC-20 (inflated totalSupply) | False compliance | Register only audited tokens; avoid rebasing tokens without adapter |
| Governance capture | Parameter / role theft | Timelock, split roles (`GUARDIAN`, `PAUSER`), multi-sig admin |
| API layer spoofing | Misleading dashboard | API reads **only** from published `ComplianceCore` addresses; publish ABIs |
## External audit scope (recommended)
1. `contracts/hybx-omnl/*.sol` — policy rounding, reentrancy (minimal external calls), access control.
2. `OMNLMirrorReceiver` — decoding, version monotonicity, selector allowlist.
3. Operational: HSM usage for `commitReserve`, CCIP operational runbooks.
Structured checklist: [EXTERNAL_AUDIT_CHECKLIST.md](EXTERNAL_AUDIT_CHECKLIST.md).
## Key ceremony (outline)
- Generate `RESERVE_COMMITTER` / admin keys in HSM; no plaintext long-lived prod keys in CI.
- Document signer roster on-chain (`bytes32` merkle root optional extension) for auditor parity.

View File

@@ -0,0 +1,9 @@
# ZK reserve attestation (optional extension)
On-chain policy uses public inputs `(S0, S1, R)` from `ERC20.totalSupply` and `ReserveCommitmentStore`. For **privacy-preserving** proof that backing exists without disclosing raw bank balances:
1. Implement a **verifier contract** conforming to [`IZkReserveProofVerifier.sol`](../../contracts/hybx-omnl/interfaces/IZkReserveProofVerifier.sol).
2. Off-chain: generate proofs with your chosen circuit (public inputs: commitment to R, line id, snapshot block).
3. Wire: either wrap `commitReserve` with a step that checks `verifyProof` before accepting R, or post proofs alongside `evidenceHash` / `merkleRoot` for auditor tooling only.
This repo does not ship a production circuit or verifier bytecode; the interface is the integration boundary for audits.

View File

@@ -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,22 @@ 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"
[profile.chain138_legacy]
optimizer = true
optimizer_runs = 100
via_ir = true
# Chain 138 currently rejects PUSH0 bytecode as well; target Paris for gas-canonical deployment flows.
evm_version = "paris"
# RPC endpoints — use: forge create ... --rpc-url chain138
# Prevents default localhost:8545 when ETH_RPC_URL not set
@@ -66,6 +89,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" }

View File

@@ -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')
})
})

View File

@@ -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,
},
},

View File

@@ -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",

View File

@@ -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");

View File

@@ -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));
}

View File

@@ -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();
}

View File

@@ -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");
}
}

View File

@@ -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,

View File

@@ -0,0 +1,25 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import {Script, console2} from "forge-std/Script.sol";
import {OMNLMirrorCoordinator} from "../../contracts/hybx-omnl/OMNLMirrorCoordinator.sol";
/// @notice Deploy OMNLMirrorCoordinator and configure mirror destination (CCIP router + reserve store from env).
contract DeployMirrorCoordinator is Script {
function run() external {
uint256 pk = vm.envUint("PRIVATE_KEY");
address admin = vm.addr(pk);
address router = vm.envAddress("OMNL_CCIP_ROUTER");
address store = vm.envAddress("OMNL_RESERVE_COMMITMENT_STORE");
address receiver = vm.envAddress("OMNL_MIRROR_RECEIVER");
uint64 destChainSelector = uint64(vm.envUint("OMNL_CCIP_DEST_CHAIN_SELECTOR"));
address feeToken = vm.envAddress("OMNL_CCIP_FEE_TOKEN");
vm.startBroadcast(pk);
OMNLMirrorCoordinator coord = new OMNLMirrorCoordinator(router, store, admin);
coord.setMirrorDestination(destChainSelector, receiver, feeToken);
vm.stopBroadcast();
console2.log("OMNLMirrorCoordinator", address(coord));
}
}

View File

@@ -0,0 +1,30 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import {Script, console2} from "forge-std/Script.sol";
import {InstrumentRegistry} from "../../contracts/hybx-omnl/InstrumentRegistry.sol";
import {ReserveCommitmentStore} from "../../contracts/hybx-omnl/ReserveCommitmentStore.sol";
import {OMNLCircuitBreaker} from "../../contracts/hybx-omnl/OMNLCircuitBreaker.sol";
import {ComplianceCore} from "../../contracts/hybx-omnl/ComplianceCore.sol";
/// @notice Deploy core OMNL stack (registry, reserves, breakers, compliance). Mirror receiver is chain-specific.
contract DeployOMNLStack is Script {
function run() external {
uint256 pk = vm.envUint("PRIVATE_KEY");
address admin = vm.addr(pk);
vm.startBroadcast(pk);
InstrumentRegistry registry = new InstrumentRegistry(admin);
ReserveCommitmentStore reserves = new ReserveCommitmentStore(admin);
OMNLCircuitBreaker breakers = new OMNLCircuitBreaker(admin);
ComplianceCore core = new ComplianceCore(address(registry), address(reserves), address(breakers));
vm.stopBroadcast();
console2.log("InstrumentRegistry", address(registry));
console2.log("ReserveCommitmentStore", address(reserves));
console2.log("OMNLCircuitBreaker", address(breakers));
console2.log("ComplianceCore", address(core));
}
}

View File

@@ -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();
}

View File

@@ -0,0 +1,21 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import {Script, console2} from "forge-std/Script.sol";
import {PolicyProfileRegistry} from "../../contracts/universal-resource/PolicyProfileRegistry.sol";
/// @notice Deploy standalone URA policy profile anchor (not full GRU M00). Set POLICY_PROFILE_REGISTRY_ADMIN or defaults to deployer.
contract DeployPolicyProfileRegistry is Script {
function run() external {
uint256 pk = vm.envUint("PRIVATE_KEY");
address deployer = vm.addr(pk);
address admin = vm.envOr("POLICY_PROFILE_REGISTRY_ADMIN", deployer);
vm.startBroadcast(pk);
PolicyProfileRegistry reg = new PolicyProfileRegistry(admin);
vm.stopBroadcast();
console2.log("PolicyProfileRegistry", address(reg));
console2.log("admin", admin);
}
}

View File

@@ -0,0 +1,404 @@
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
PROJECT_ROOT="$(cd "$REPO_ROOT/.." && pwd)"
BUNDLE_JSON="$REPO_ROOT/config/gas-pmm-execution-bundle.json"
OUT_DIR="$PROJECT_ROOT/reports/status"
OUT_JSON="$OUT_DIR/gas-pmm-execution-bundle-latest.json"
OUT_MD="$OUT_DIR/gas-pmm-execution-bundle-latest.md"
source "$REPO_ROOT/scripts/lib/deployment/dotenv.sh"
load_deployment_env --repo-root "$REPO_ROOT"
mkdir -p "$OUT_DIR"
json_escape() {
jq -Rn --arg v "${1:-}" '$v'
}
resolve_env_from_list() {
local joined="${1:-}"
local value=""
local key
[[ -z "$joined" ]] && return 0
IFS='|' read -r -a keys <<< "$joined"
for key in "${keys[@]}"; do
if [[ -n "${!key:-}" ]]; then
value="${!key}"
break
fi
done
printf '%s' "$value"
}
hex_to_addr() {
local value="${1#0x}"
[[ ${#value} -ge 40 ]] || return 1
printf '0x%s\n' "${value: -40}"
}
code_status() {
local rpc="$1"
local address="$2"
if [[ -z "$rpc" || -z "$address" ]]; then
printf 'missing'
return
fi
local code=""
code="$(timeout 12s cast code --rpc-url "$rpc" "$address" 2>/dev/null || true)"
if [[ -n "$code" && "$code" != "0x" ]]; then
printf 'has_code'
else
printf 'no_code'
fi
}
symbol_call() {
local rpc="$1"
local address="$2"
local value
value="$(timeout 8s cast call --rpc-url "$rpc" "$address" "symbol()(string)" 2>/dev/null || true)"
value="${value#\"}"
value="${value%\"}"
printf '%s' "$value"
}
normalize_key() {
printf '%s' "$1" | tr '[:lower:]- ' '[:upper:]__'
}
append_finding() {
local finding="$1"
if [[ -n "$FINDINGS_JOINED" ]]; then
FINDINGS_JOINED+="|$finding"
else
FINDINGS_JOINED="$finding"
fi
}
emit_findings_json() {
local joined="${1:-}"
if [[ -z "$joined" ]]; then
printf '[]'
return
fi
jq -Rn --arg joined "$joined" '$joined | split("|") | map(select(length > 0))'
}
rollout_order_json="$(jq -c '[.chains[] | {chainId, network}]' "$BUNDLE_JSON")"
generated_at="$(date -u +"%Y-%m-%dT%H:%M:%SZ")"
json_file="$(mktemp)"
md_file="$(mktemp)"
{
echo "{"
echo " \"generatedAt\": $(json_escape "$generated_at"),"
echo " \"bundle\": $(jq '.bundleName' "$BUNDLE_JSON"),"
echo " \"description\": $(jq '.description' "$BUNDLE_JSON"),"
echo " \"poolDefaults\": $(jq '.poolDefaults' "$BUNDLE_JSON"),"
echo " \"rolloutOrder\": $rollout_order_json,"
echo " \"chains\": ["
} >"$json_file"
{
echo "# Gas PMM Execution Bundle"
echo
echo "Generated at: \`$generated_at\`"
echo
echo "This bundle covers the remaining \`22\` planned gas-family DODO PMM rows across \`11\` public chains."
echo
echo "Execution posture:"
echo
echo "- Each chain has two target pools: wrapped-native self-quote and wrapped-native versus stable."
echo "- The current expected pool addresses are still scaffold slots, so \`no_code\` is the normal pre-deploy state."
echo "- Stable-quote pool creation needs a fresh \`STABLE_PRICE_1E18\` input at execution time."
echo "- Wrapped-native quote defaults in this bundle use the repo's canonical or documented live contracts instead of the old \`0xaa...\` placeholders."
echo "- The deploy step reuses the legacy integration constructor slots; those seed addresses are only deployment anchors, while the gas pairs themselves are created through generic \`createPool(...)\` calls."
} >"$md_file"
first_chain=1
chain_count=0
ready_count=0
blocked_count=0
while IFS= read -r chain_row; do
chain_id="$(jq -r '.chainId' <<<"$chain_row")"
network="$(jq -r '.network' <<<"$chain_row")"
chain_key="$(jq -r '.chainKey' <<<"$chain_row")"
rpc_envs_joined="$(jq -r '.rpcEnv | join("|")' <<<"$chain_row")"
integration_env="$(jq -r '.integrationEnv' <<<"$chain_row")"
dvm_envs_joined="$(jq -r '.dodoVendingMachineEnv | join("|")' <<<"$chain_row")"
rpc="$(resolve_env_from_list "$rpc_envs_joined")"
integration="${!integration_env:-}"
dvm="$(resolve_env_from_list "$dvm_envs_joined")"
base_symbol="$(jq -r '.baseToken.symbol' <<<"$chain_row")"
base_address="$(jq -r '.baseToken.address' <<<"$chain_row")"
wrapped_symbol="$(jq -r '.wrappedNativeQuote.symbol' <<<"$chain_row")"
wrapped_envs_joined="$(jq -r '.wrappedNativeQuote.env | join("|")' <<<"$chain_row")"
wrapped_default="$(jq -r '.wrappedNativeQuote.default' <<<"$chain_row")"
wrapped_note="$(jq -r '.wrappedNativeQuote.note' <<<"$chain_row")"
wrapped_address="$(resolve_env_from_list "$wrapped_envs_joined")"
wrapped_address="${wrapped_address:-$wrapped_default}"
stable_symbol="$(jq -r '.stableQuote.symbol' <<<"$chain_row")"
stable_envs_joined="$(jq -r '.stableQuote.env | join("|")' <<<"$chain_row")"
stable_default="$(jq -r '.stableQuote.default' <<<"$chain_row")"
stable_note="$(jq -r '.stableQuote.note' <<<"$chain_row")"
stable_address="$(resolve_env_from_list "$stable_envs_joined")"
stable_address="${stable_address:-$stable_default}"
integration_status="$(code_status "$rpc" "$integration")"
dvm_status="$(code_status "$rpc" "$dvm")"
base_status="$(code_status "$rpc" "$base_address")"
wrapped_status="$(code_status "$rpc" "$wrapped_address")"
stable_status="$(code_status "$rpc" "$stable_address")"
wrapped_pair_row="$(jq -c '.pairs[] | select(.quoteSymbol == $quote)' --arg quote "$wrapped_symbol" <<<"$chain_row")"
stable_pair_row="$(jq -c '.pairs[] | select(.quoteSymbol == $quote)' --arg quote "$stable_symbol" <<<"$chain_row")"
wrapped_pair_name="$(jq -r '.pair' <<<"$wrapped_pair_row")"
stable_pair_name="$(jq -r '.pair' <<<"$stable_pair_row")"
wrapped_pool_expected="$(jq -r '.expectedPoolAddress' <<<"$wrapped_pair_row")"
stable_pool_expected="$(jq -r '.expectedPoolAddress' <<<"$stable_pair_row")"
wrapped_pool_status="$(code_status "$rpc" "$wrapped_pool_expected")"
stable_pool_status="$(code_status "$rpc" "$stable_pool_expected")"
FINDINGS_JOINED=""
[[ -z "$rpc" ]] && append_finding "missing_rpc"
[[ "$dvm_status" != "has_code" ]] && append_finding "missing_or_invalid_dodo_vending_machine"
[[ "$base_status" != "has_code" ]] && append_finding "gas_mirror_base_missing_or_no_code"
[[ "$wrapped_status" != "has_code" ]] && append_finding "wrapped_native_quote_missing_or_no_code"
[[ "$stable_status" != "has_code" ]] && append_finding "stable_quote_missing_or_no_code"
if [[ -n "$integration" && "$integration_status" == "no_code" ]]; then
append_finding "integration_env_set_but_no_code"
fi
if [[ -z "$integration" || "$integration_status" != "has_code" ]]; then
append_finding "integration_needs_deploy"
fi
[[ "$wrapped_pool_status" != "has_code" ]] && append_finding "wrapped_native_pool_not_live"
[[ "$stable_pool_status" != "has_code" ]] && append_finding "stable_quote_pool_not_live"
readiness="blocked"
if [[ -n "$rpc" && "$dvm_status" == "has_code" && "$base_status" == "has_code" && "$wrapped_status" == "has_code" && "$stable_status" == "has_code" ]]; then
readiness="ready_for_execution"
fi
if [[ "$readiness" == "ready_for_execution" ]]; then
ready_count=$((ready_count + 1))
else
blocked_count=$((blocked_count + 1))
fi
chain_count=$((chain_count + 1))
normalized_chain_key="$(normalize_key "$chain_key")"
forge_profile="default"
if [[ "$chain_id" == "25" ]]; then
forge_profile="cronos_legacy"
fi
dvm_primary_env="$(jq -r '.dodoVendingMachineEnv[0]' <<<"$chain_row")"
deploy_cmd=$(cat <<EOF
cd $REPO_ROOT
DODO_VENDING_MACHINE_ADDRESS=\${$dvm_primary_env:-$dvm} \\
OFFICIAL_USDT_ADDRESS=$stable_address \\
OFFICIAL_USDC_ADDRESS=$wrapped_address \\
COMPLIANT_USDT_ADDRESS=$base_address \\
COMPLIANT_USDC_ADDRESS=$base_address \\
FOUNDRY_PROFILE=$forge_profile \\
forge script script/dex/DeployDODOPMMIntegration.s.sol:DeployDODOPMMIntegration \\
--rpc-url $rpc \\
--chain-id $chain_id \\
--broadcast \\
--private-key "\$PRIVATE_KEY" \\
-vvv
jq -r '.transactions[] | select(.transactionType=="CREATE" and .contractName=="DODOPMMIntegration") | .contractAddress' \\
broadcast/DeployDODOPMMIntegration.s.sol/$chain_id/run-latest.json
EOF
)
create_cmd=$(cat <<EOF
cd $REPO_ROOT
INTEGRATION_ADDRESS=\${$integration_env:-REPLACE_WITH_DEPLOYED_INTEGRATION}
LP_FEE_RATE=\${LP_FEE_RATE:-3}
SELF_QUOTE_PRICE_1E18=1000000000000000000
STABLE_PRICE_1E18=\${STABLE_PRICE_1E18:-REPLACE_WITH_LIVE_${normalized_chain_key}_GAS_PRICE_1E18}
K_FACTOR=\${K_FACTOR:-500000000000000000}
ENABLE_TWAP=\${ENABLE_TWAP:-true}
cast send "\$INTEGRATION_ADDRESS" \\
"createPool(address,address,uint256,uint256,uint256,bool)" \\
$base_address \\
$wrapped_address \\
"\$LP_FEE_RATE" \\
"\$SELF_QUOTE_PRICE_1E18" \\
"\$K_FACTOR" \\
"\$ENABLE_TWAP" \\
--rpc-url $rpc \\
--private-key "\$PRIVATE_KEY" \\
--legacy \\
-vv
cast send "\$INTEGRATION_ADDRESS" \\
"createPool(address,address,uint256,uint256,uint256,bool)" \\
$base_address \\
$stable_address \\
"\$LP_FEE_RATE" \\
"\$STABLE_PRICE_1E18" \\
"\$K_FACTOR" \\
"\$ENABLE_TWAP" \\
--rpc-url $rpc \\
--private-key "\$PRIVATE_KEY" \\
--legacy \\
-vv
EOF
)
verify_cmd=$(cat <<EOF
cast code --rpc-url $rpc $wrapped_pool_expected
cast code --rpc-url $rpc $stable_pool_expected
cast call --rpc-url $rpc \${$integration_env:-REPLACE_WITH_DEPLOYED_INTEGRATION} 'pools(address,address)(address)' $base_address $wrapped_address
cast call --rpc-url $rpc \${$integration_env:-REPLACE_WITH_DEPLOYED_INTEGRATION} 'pools(address,address)(address)' $base_address $stable_address
EOF
)
if [[ $first_chain -eq 0 ]]; then
echo "," >>"$json_file"
fi
first_chain=0
cat >>"$json_file" <<EOF
{
"chainId": $chain_id,
"network": $(json_escape "$network"),
"chainKey": $(json_escape "$chain_key"),
"rpc": $(json_escape "$rpc"),
"readiness": $(json_escape "$readiness"),
"blockingFindings": $(emit_findings_json "$FINDINGS_JOINED"),
"integrationEnv": $(json_escape "$integration_env"),
"integrationAddress": $(json_escape "$integration"),
"integrationStatus": $(json_escape "$integration_status"),
"dodoVendingMachine": {
"envCandidates": $(jq '.dodoVendingMachineEnv' <<<"$chain_row"),
"address": $(json_escape "$dvm"),
"status": $(json_escape "$dvm_status")
},
"baseToken": {
"symbol": $(json_escape "$base_symbol"),
"address": $(json_escape "$base_address"),
"status": $(json_escape "$base_status"),
"symbolOnChain": $(json_escape "$(symbol_call "$rpc" "$base_address")")
},
"wrappedNativeQuote": {
"symbol": $(json_escape "$wrapped_symbol"),
"envCandidates": $(jq '.wrappedNativeQuote.env' <<<"$chain_row"),
"address": $(json_escape "$wrapped_address"),
"default": $(json_escape "$wrapped_default"),
"status": $(json_escape "$wrapped_status"),
"symbolOnChain": $(json_escape "$(symbol_call "$rpc" "$wrapped_address")"),
"note": $(json_escape "$wrapped_note")
},
"stableQuote": {
"symbol": $(json_escape "$stable_symbol"),
"envCandidates": $(jq '.stableQuote.env' <<<"$chain_row"),
"address": $(json_escape "$stable_address"),
"default": $(json_escape "$stable_default"),
"status": $(json_escape "$stable_status"),
"symbolOnChain": $(json_escape "$(symbol_call "$rpc" "$stable_address")"),
"note": $(json_escape "$stable_note")
},
"pairs": [
{
"pair": $(json_escape "$wrapped_pair_name"),
"expectedPoolAddress": $(json_escape "$wrapped_pool_expected"),
"poolCodeStatus": $(json_escape "$wrapped_pool_status"),
"executionPriceMode": "self_quote_fixed_1e18"
},
{
"pair": $(json_escape "$stable_pair_name"),
"expectedPoolAddress": $(json_escape "$stable_pool_expected"),
"poolCodeStatus": $(json_escape "$stable_pool_status"),
"executionPriceMode": "live_stable_spot_required"
}
],
"commands": {
"deployIntegration": $(json_escape "$deploy_cmd"),
"createPools": $(json_escape "$create_cmd"),
"verifyPools": $(json_escape "$verify_cmd")
}
}
EOF
{
echo
echo "## $network ($chain_id)"
echo
echo "Readiness: \`$readiness\`"
echo
echo "| Item | Address | Status |"
echo "| --- | --- | --- |"
echo "| DODO vending machine | \`${dvm:-unset}\` | \`$dvm_status\` |"
echo "| Integration env \`$integration_env\` | \`${integration:-unset}\` | \`$integration_status\` |"
echo "| $base_symbol | \`$base_address\` | \`$base_status\` |"
echo "| $wrapped_symbol | \`$wrapped_address\` | \`$wrapped_status\` |"
echo "| $stable_symbol | \`$stable_address\` | \`$stable_status\` |"
echo "| Pool $wrapped_pair_name | \`$wrapped_pool_expected\` | \`$wrapped_pool_status\` |"
echo "| Pool $stable_pair_name | \`$stable_pool_expected\` | \`$stable_pool_status\` |"
echo
echo "Blocking findings:"
if [[ -n "$FINDINGS_JOINED" ]]; then
IFS='|' read -r -a findings_array <<< "$FINDINGS_JOINED"
for finding in "${findings_array[@]}"; do
echo "- \`$finding\`"
done
else
echo "- None"
fi
echo
echo "Quote token notes:"
echo
echo "- $wrapped_symbol: $wrapped_note"
echo "- $stable_symbol: $stable_note"
echo
echo "Deploy integration:"
echo
echo '```bash'
echo "$deploy_cmd"
echo '```'
echo
echo "Create pools:"
echo
echo '```bash'
echo "$create_cmd"
echo '```'
echo
echo "Verify pools:"
echo
echo '```bash'
echo "$verify_cmd"
echo '```'
} >>"$md_file"
done < <(jq -c '.chains[]' "$BUNDLE_JSON")
{
echo
echo " ],"
echo " \"summary\": {"
echo " \"chains\": $chain_count,"
echo " \"pairs\": $((chain_count * 2)),"
echo " \"readyForExecution\": $ready_count,"
echo " \"blocked\": $blocked_count"
echo " }"
echo "}"
} >>"$json_file"
jq . "$json_file" >"$OUT_JSON"
mv "$md_file" "$OUT_MD"
rm -f "$json_file"
echo "Wrote:"
echo " $OUT_JSON"
echo " $OUT_MD"

View File

@@ -0,0 +1,284 @@
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
PROJECT_ROOT="$(cd "$REPO_ROOT/.." && pwd)"
BUNDLE_JSON="$REPO_ROOT/config/optimism-cronos-dodo-4-pools-execution-bundle.json"
OUT_DIR="$PROJECT_ROOT/reports/status"
OUT_JSON="$OUT_DIR/optimism-cronos-dodo-4-pools-execution-bundle-latest.json"
OUT_MD="$OUT_DIR/optimism-cronos-dodo-4-pools-execution-bundle-latest.md"
source "$REPO_ROOT/scripts/lib/deployment/dotenv.sh"
load_deployment_env --repo-root "$REPO_ROOT"
mkdir -p "$OUT_DIR"
json_escape() {
jq -Rn --arg v "${1:-}" '$v'
}
resolve_env_from_list() {
local joined="$1"
local value=""
local key
IFS='|' read -r -a _keys <<< "$joined"
for key in "${_keys[@]}"; do
if [[ -n "${!key:-}" ]]; then
value="${!key}"
break
fi
done
printf '%s' "$value"
}
code_status() {
local rpc="$1"
local address="$2"
if [[ -z "$rpc" || -z "$address" ]]; then
printf 'missing'
return
fi
local code=""
code="$(cast code --rpc-url "$rpc" "$address" 2>/dev/null || true)"
if [[ -n "$code" && "$code" != "0x" ]]; then
printf 'has_code'
else
printf 'no_code'
fi
}
symbol_call() {
local rpc="$1"
local address="$2"
local value
value="$(cast call --rpc-url "$rpc" "$address" "symbol()(string)" 2>/dev/null || true)"
value="${value#\"}"
value="${value%\"}"
printf '%s' "$value"
}
json_file="$(mktemp)"
md_file="$(mktemp)"
generated_at="$(date -u +"%Y-%m-%dT%H:%M:%SZ")"
{
echo "{"
echo " \"generatedAt\": $(json_escape "$generated_at"),"
echo " \"bundle\": $(jq '.bundleName' "$BUNDLE_JSON"),"
echo " \"chains\": ["
} >"$json_file"
{
echo "# Optimism/Cronos DODO 4-Pool Execution Bundle"
echo
echo "Generated at: \`$generated_at\`"
echo
echo "This bundle covers the four remaining \`configured_no_code\` DODO PMM pools:"
echo
echo "- Optimism: \`cWUSDC/USDC\`, \`cWUSDT/USDT\`"
echo "- Cronos: \`cWUSDC/USDC\`, \`cWUSDT/USDT\`"
echo
echo "Execution posture:"
echo
echo "- DODO vending-machine addresses are present in env."
echo "- Compliant \`cWUSDT\` / \`cWUSDC\` token contracts have bytecode on both chains."
echo "- The expected pool addresses still have no code on both chains."
echo "- The canonical quote anchors for Optimism/Cronos are the chain-native official USDT/USDC addresses below."
} >"$md_file"
first_chain=1
while IFS= read -r chain_row; do
chain_id="$(jq -r '.chainId' <<<"$chain_row")"
network="$(jq -r '.network' <<<"$chain_row")"
integration_env="$(jq -r '.integrationEnv' <<<"$chain_row")"
dvm_env="$(jq -r '.dodoVendingMachineEnv' <<<"$chain_row")"
rpc_envs_joined="$(jq -r '.rpcEnv | join("|")' <<<"$chain_row")"
rpc="$(resolve_env_from_list "$rpc_envs_joined")"
dvm="${!dvm_env:-}"
integration="${!integration_env:-}"
official_usdc_env="$(jq -r '.official.USDC.env' <<<"$chain_row")"
official_usdc_default="$(jq -r '.official.USDC.default' <<<"$chain_row")"
official_usdt_env="$(jq -r '.official.USDT.env' <<<"$chain_row")"
official_usdt_default="$(jq -r '.official.USDT.default' <<<"$chain_row")"
official_usdc="${!official_usdc_env:-$official_usdc_default}"
official_usdt="${!official_usdt_env:-$official_usdt_default}"
cwusdc_env="$(jq -r '.compliant.cWUSDC.env' <<<"$chain_row")"
cwusdt_env="$(jq -r '.compliant.cWUSDT.env' <<<"$chain_row")"
cwusdc="${!cwusdc_env:-}"
cwusdt="${!cwusdt_env:-}"
integration_status="$(code_status "$rpc" "$integration")"
dvm_status="$(code_status "$rpc" "$dvm")"
official_usdc_status="$(code_status "$rpc" "$official_usdc")"
official_usdt_status="$(code_status "$rpc" "$official_usdt")"
cwusdc_status="$(code_status "$rpc" "$cwusdc")"
cwusdt_status="$(code_status "$rpc" "$cwusdt")"
pool_usdc_expected="$(jq -r '.pairs[] | select(.pair=="cWUSDC/USDC") | .expectedPoolAddress' <<<"$chain_row")"
pool_usdt_expected="$(jq -r '.pairs[] | select(.pair=="cWUSDT/USDT") | .expectedPoolAddress' <<<"$chain_row")"
pool_usdc_status="$(code_status "$rpc" "$pool_usdc_expected")"
pool_usdt_status="$(code_status "$rpc" "$pool_usdt_expected")"
forge_profile="default"
if [[ "$chain_id" == "25" ]]; then
forge_profile="cronos_legacy"
fi
deploy_cmd=$(cat <<EOF
cd $REPO_ROOT
DODO_VENDING_MACHINE_ADDRESS=$dvm \\
OFFICIAL_USDT_ADDRESS=$official_usdt \\
OFFICIAL_USDC_ADDRESS=$official_usdc \\
COMPLIANT_USDT_ADDRESS=$cwusdt \\
COMPLIANT_USDC_ADDRESS=$cwusdc \\
FOUNDRY_PROFILE=$forge_profile \\
forge script script/dex/DeployDODOPMMIntegration.s.sol:DeployDODOPMMIntegration \\
--rpc-url $rpc \\
--chain-id $chain_id \\
--broadcast \\
--private-key "\$PRIVATE_KEY" \\
-vvv
jq -r '.transactions[] | select(.transactionType=="CREATE" and .contractName=="DODOPMMIntegration") | .contractAddress' \\
broadcast/DeployDODOPMMIntegration.s.sol/$chain_id/run-latest.json
EOF
)
create_cmd=$(cat <<EOF
cd $REPO_ROOT
DODO_PMM_INTEGRATION_ADDRESS=\${$integration_env:-REPLACE_WITH_DEPLOYED_INTEGRATION} \\
RPC_URL=$rpc \\
COMPLIANT_USDT_ADDRESS=$cwusdt \\
COMPLIANT_USDC_ADDRESS=$cwusdc \\
OFFICIAL_USDT_ADDRESS=$official_usdt \\
OFFICIAL_USDC_ADDRESS=$official_usdc \\
PRIVATE_KEY="\$PRIVATE_KEY" \\
bash scripts/setup-dodo-pools.sh
EOF
)
verify_cmd=$(cat <<EOF
cast code --rpc-url $rpc $pool_usdc_expected
cast code --rpc-url $rpc $pool_usdt_expected
cast call --rpc-url $rpc \${$integration_env:-REPLACE_WITH_DEPLOYED_INTEGRATION} 'pools(address,address)(address)' $cwusdc $official_usdc
cast call --rpc-url $rpc \${$integration_env:-REPLACE_WITH_DEPLOYED_INTEGRATION} 'pools(address,address)(address)' $cwusdt $official_usdt
EOF
)
if [[ $first_chain -eq 0 ]]; then
echo "," >>"$json_file"
fi
first_chain=0
cat >>"$json_file" <<EOF
{
"chainId": $chain_id,
"network": $(json_escape "$network"),
"rpc": $(json_escape "$rpc"),
"integrationEnv": $(json_escape "$integration_env"),
"integrationAddress": $(json_escape "$integration"),
"integrationStatus": $(json_escape "$integration_status"),
"dodoVendingMachine": {
"env": $(json_escape "$dvm_env"),
"address": $(json_escape "$dvm"),
"status": $(json_escape "$dvm_status")
},
"officialTokens": {
"USDC": {
"env": $(json_escape "$official_usdc_env"),
"address": $(json_escape "$official_usdc"),
"status": $(json_escape "$official_usdc_status"),
"symbol": $(json_escape "$(symbol_call "$rpc" "$official_usdc")")
},
"USDT": {
"env": $(json_escape "$official_usdt_env"),
"address": $(json_escape "$official_usdt"),
"status": $(json_escape "$official_usdt_status"),
"symbol": $(json_escape "$(symbol_call "$rpc" "$official_usdt")")
}
},
"compliantTokens": {
"cWUSDC": {
"env": $(json_escape "$cwusdc_env"),
"address": $(json_escape "$cwusdc"),
"status": $(json_escape "$cwusdc_status"),
"symbol": $(json_escape "$(symbol_call "$rpc" "$cwusdc")")
},
"cWUSDT": {
"env": $(json_escape "$cwusdt_env"),
"address": $(json_escape "$cwusdt"),
"status": $(json_escape "$cwusdt_status"),
"symbol": $(json_escape "$(symbol_call "$rpc" "$cwusdt")")
}
},
"pairs": [
{
"pair": "cWUSDC/USDC",
"expectedPoolAddress": $(json_escape "$pool_usdc_expected"),
"poolCodeStatus": $(json_escape "$pool_usdc_status")
},
{
"pair": "cWUSDT/USDT",
"expectedPoolAddress": $(json_escape "$pool_usdt_expected"),
"poolCodeStatus": $(json_escape "$pool_usdt_status")
}
],
"commands": {
"deployIntegration": $(json_escape "$deploy_cmd"),
"createPools": $(json_escape "$create_cmd"),
"verifyPools": $(json_escape "$verify_cmd")
}
}
EOF
{
echo
echo "## $network ($chain_id)"
echo
echo "| Item | Address | Status |"
echo "| --- | --- | --- |"
echo "| DODO vending machine | \`$dvm\` | \`$dvm_status\` |"
echo "| Integration env \`$integration_env\` | \`${integration:-unset}\` | \`$integration_status\` |"
echo "| Official USDC | \`$official_usdc\` | \`$official_usdc_status\` |"
echo "| Official USDT | \`$official_usdt\` | \`$official_usdt_status\` |"
echo "| cWUSDC | \`$cwusdc\` | \`$cwusdc_status\` |"
echo "| cWUSDT | \`$cwusdt\` | \`$cwusdt_status\` |"
echo "| Pool cWUSDC/USDC | \`$pool_usdc_expected\` | \`$pool_usdc_status\` |"
echo "| Pool cWUSDT/USDT | \`$pool_usdt_expected\` | \`$pool_usdt_status\` |"
echo
echo "Deploy integration:"
echo
echo '```bash'
echo "$deploy_cmd"
echo '```'
echo
echo "Create pools:"
echo
echo '```bash'
echo "$create_cmd"
echo '```'
echo
echo "Verify pools:"
echo
echo '```bash'
echo "$verify_cmd"
echo '```'
} >>"$md_file"
done < <(jq -c '.chains[]' "$BUNDLE_JSON")
{
echo
echo " ]"
echo "}"
} >>"$json_file"
jq . "$json_file" >"$OUT_JSON"
mv "$md_file" "$OUT_MD"
rm -f "$json_file"
echo "Wrote:"
echo " $OUT_JSON"
echo " $OUT_MD"

View File

@@ -0,0 +1,232 @@
#!/usr/bin/env bash
# Chain 138 → Ethereum Mainnet only: bridge PCT of each canonical c* balance via CWMultiTokenBridgeL1.
# Default PCT: 17.25% (= 1725 basis points of 10000; override with PCT_BP).
#
# Modes:
# --plan-only Write JSON + summary (default)
# --check-routes destinations(token, Mainnet selector) for each token
# --emit-cmds Print approve + lockAndSend cast lines (review before running)
#
# Env: PRIVATE_KEY (for --emit-cmds deployer address), RPC_URL_138, CW_L1_BRIDGE_CHAIN138,
# LINK_TOKEN_CHAIN138, RECIPIENT_ADDRESS (default: deployer)
# PCT_BP (default 1725 = 17.25%)
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
SMOM_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
PROXMOX_ROOT="$(cd "$SMOM_ROOT/.." && pwd)"
cd "$SMOM_ROOT"
MODE="plan"
while [[ $# -gt 0 ]]; do
case "$1" in
--plan-only) MODE="plan" ;;
--check-routes) MODE="check" ;;
--emit-cmds) MODE="emit" ;;
--help|-h)
grep '^#' "$0" | head -22
exit 0
;;
*) echo "Unknown: $1"; exit 1 ;;
esac
shift || true
done
if [[ -f "$PROXMOX_ROOT/scripts/lib/load-project-env.sh" ]]; then
# shellcheck disable=SC1090
PROJECT_ROOT="$PROXMOX_ROOT" source "$PROXMOX_ROOT/scripts/lib/load-project-env.sh"
elif [[ -f .env ]]; then
set -a && source .env && set +a
fi
PCT_BP="${PCT_BP:-1725}"
RPC="${RPC_URL_138:-${CHAIN138_RPC:-${RPC_URL:-http://192.168.11.211:8545}}}"
OUT_JSON="${OUT_JSON:-$SMOM_ROOT/reports/status/c138-bridge-mainnet-pct-latest.json}"
BRIDGE="${CW_L1_BRIDGE_CHAIN138:-}"
DEPLOYER=""
if [[ -n "${PRIVATE_KEY:-}" ]]; then
DEPLOYER="$(cast wallet address "$PRIVATE_KEY" 2>/dev/null || true)"
fi
RECIPIENT="${RECIPIENT_ADDRESS:-$DEPLOYER}"
MAINNET_SEL=5009297550715157269
export RPC OUT_JSON DEPLOYER RECIPIENT BRIDGE PCT_BP MAINNET_SEL
# Canonical c* only (EXPLORER_TOKEN_LIST_CROSSCHECK §5)
read -r -d '' TOKEN_ROWS << 'EOF' || true
cUSDT:0x93E66202A11B1772E55407B32B44e5Cd8eda7f22
cUSDC:0xf22258f57794CC8E06237084b353Ab30fFfa640b
cEURC:0x8085961F9cF02b4d800A3c6d386D31da4B34266a
cEURT:0xdf4b71c61E5912712C1Bdd451416B9aC26949d72
cGBPC:0x003960f16D9d34F2e98d62723B6721Fb92074aD2
cGBPT:0x350f54e4D23795f86A9c03988c7135357CCaD97c
cAUDC:0xD51482e567c03899eecE3CAe8a058161FD56069D
cJPYC:0xEe269e1226a334182aace90056EE4ee5Cc8A6770
cCHFC:0x873990849DDa5117d7C644f0aF24370797C03885
cCADC:0x54dBd40cF05e15906A2C21f600937e96787f5679
cXAUC:0x290E52a8819A4fbD0714E517225429aA2B70EC6b
cXAUT:0x94e408E26c6FD8F4ee00b54dF19082FDA07dC96E
EOF
export TOKEN_ROWS
mkdir -p "$(dirname "$OUT_JSON")"
python3 << PY
import json, subprocess, os, re
rpc = os.environ.get("RPC", "http://192.168.11.211:8545")
deployer = os.environ.get("DEPLOYER", "")
recipient = os.environ.get("RECIPIENT", deployer)
bridge = os.environ.get("BRIDGE", "")
pct_bp = int(os.environ.get("PCT_BP", "1725"))
sel = int(os.environ.get("MAINNET_SEL", "5009297550715157269"))
rows = []
for line in os.environ.get("TOKEN_ROWS", "").strip().split("\n"):
if not line.strip():
continue
sym, addr = line.split(":", 1)
rows.append((sym.strip(), addr.strip()))
def balance_of(addr):
if not deployer:
return None
r = subprocess.run(
["cast", "call", addr, "balanceOf(address)(uint256)", deployer, "--rpc-url", rpc],
capture_output=True, text=True,
)
if r.returncode != 0:
return None
m = re.match(r"^\s*(\d+)", r.stdout.strip())
return int(m.group(1)) if m else None
plan = {
"schema": "c138-bridge-mainnet-pct/v1",
"rpc_url": rpc,
"deployer": deployer,
"recipient": recipient,
"cw_l1_bridge": bridge,
"destination": "Ethereum Mainnet",
"chain_selector": str(sel),
"pct_basis_points": pct_bp,
"pct_human": f"{pct_bp / 100:.2f}%",
"tokens": [],
}
for sym, addr in rows:
bal = balance_of(addr)
if bal is None:
plan["tokens"].append({"symbol": sym, "address": addr, "error": "balance_of_failed"})
continue
amt = bal * pct_bp // 10000
plan["tokens"].append({
"symbol": sym,
"address": addr,
"balance_wei": str(bal),
"amount_to_bridge_wei": str(amt),
})
path = os.environ.get("OUT_JSON", "")
with open(path, "w") as f:
json.dump(plan, f, indent=2)
print(json.dumps({"written": path, "tokens": len(plan["tokens"])}))
PY
python3 - <<'PY'
import json, os
with open(os.environ["OUT_JSON"]) as f:
p = json.load(f)
print("\n=== c* → Mainnet only:", p.get("pct_human"), "of balance (integer base units) ===\n")
print(f"Deployer: {p.get('deployer','?')}\nRecipient: {p.get('recipient','?')}\nBridge: {p.get('cw_l1_bridge') or '(unset)'}\n")
for t in p["tokens"]:
if "error" in t:
print(f"{t['symbol']}: {t['error']}")
continue
sym = t["symbol"]
amt = int(t["amount_to_bridge_wei"])
if sym.startswith("cXAU"):
print(f"{sym:<10} bridge: {amt / 1e6:,.6f} troy oz (wei={t['amount_to_bridge_wei']})")
else:
print(f"{sym:<10} bridge: {amt / 1e6:,.6f} tokens (wei={t['amount_to_bridge_wei']})")
print("\nJSON:", os.environ["OUT_JSON"])
PY
if [[ "$MODE" == "plan" ]]; then
exit 0
fi
[[ -n "$BRIDGE" ]] || { echo "Set CW_L1_BRIDGE_CHAIN138"; exit 1; }
code=$(cast code "$BRIDGE" --rpc-url "$RPC" 2>/dev/null || echo "0x")
[[ -n "$code" && "$code" != "0x" ]] || { echo "No contract at CW_L1_BRIDGE_CHAIN138=$BRIDGE"; exit 1; }
if [[ "$MODE" == "check" ]]; then
echo ""
echo "=== Mainnet route: $BRIDGE ==="
while IFS= read -r line; do
[[ -z "$line" ]] && continue
sym="${line%%:*}"
addr="${line#*:}"
dest=$(cast call "$BRIDGE" "destinations(address,uint64)(address,bool)" "$addr" "$MAINNET_SEL" --rpc-url "$RPC" 2>/dev/null || echo "ERR")
echo "$sym ($addr): destinations(Mainnet)=$dest"
done <<< "$TOKEN_ROWS"
exit 0
fi
if [[ "$MODE" == "emit" ]]; then
[[ -n "${PRIVATE_KEY:-}" ]] || { echo "PRIVATE_KEY required for --emit-cmds"; exit 1; }
[[ -n "$RECIPIENT" ]] || { echo "RECIPIENT_ADDRESS or deployer required"; exit 1; }
LINK_TOKEN="${LINK_TOKEN_CHAIN138:-${LINK_TOKEN:-}}"
[[ -n "$LINK_TOKEN" ]] || { echo "Set LINK_TOKEN or LINK_TOKEN_CHAIN138 for fee approval lines"; exit 1; }
export LINK_TOKEN
OUT_CAST="${OUT_CAST:-$SMOM_ROOT/reports/status/c138-bridge-mainnet-pct-cast-commands.sh}"
export OUT_CAST
{
echo "#!/usr/bin/env bash"
echo "# Generated: c138-cw-bridge-mainnet-pct.sh --emit-cmds"
echo "# Review fee + reserve verifier. Fund LINK on deployer for CCIP fees."
echo "set -euo pipefail"
python3 <<'PY'
import json, os, subprocess
rpc = os.environ["RPC"]
bridge = os.environ["BRIDGE"]
recipient = os.environ["RECIPIENT"]
link = os.environ["LINK_TOKEN"]
sel = int(os.environ["MAINNET_SEL"])
with open(os.environ["OUT_JSON"]) as f:
plan = json.load(f)
for t in plan["tokens"]:
if "error" in t:
continue
amt = t.get("amount_to_bridge_wei", "0")
if int(amt) == 0:
continue
sym, token = t["symbol"], t["address"]
chk = subprocess.run(
["cast", "call", bridge, "destinations(address,uint64)(address,bool)", token, str(sel), "--rpc-url", rpc],
capture_output=True, text=True,
)
if chk.returncode != 0 or "true" not in chk.stdout:
print(f"# SKIP {sym}: destination Mainnet not enabled or query failed")
print(f"# cast output: {chk.stdout.strip()}")
continue
fee = subprocess.run(
["cast", "call", bridge,
"calculateFee(address,uint64,address,uint256)(uint256)",
token, str(sel), recipient, amt,
"--rpc-url", rpc],
capture_output=True, text=True,
)
fq = fee.stdout.strip().split()[0] if fee.returncode == 0 else "0"
print("")
print(f"# {sym} -> Mainnet amount={amt} fee_wei={fq}")
print(f"cast send {link} \"approve(address,uint256)\" {bridge} {fq} --rpc-url {rpc} --private-key \"$PRIVATE_KEY\" --legacy --gas-limit 120000")
print(f"cast send {token} \"approve(address,uint256)\" {bridge} {amt} --rpc-url {rpc} --private-key \"$PRIVATE_KEY\" --legacy --gas-limit 400000")
print(f"cast send {bridge} \"lockAndSend(address,uint64,address,uint256)\" {token} {sel} {recipient} {amt} --rpc-url {rpc} --private-key \"$PRIVATE_KEY\" --legacy --gas-limit 4000000")
PY
} > "$OUT_CAST"
chmod +x "$OUT_CAST"
echo "Wrote: $OUT_CAST"
wc -l "$OUT_CAST"
exit 0
fi

View File

@@ -30,22 +30,11 @@ fi
log_info "=== Deployment Status Check ==="
# Check .env file
if [ ! -f .env ]; then
log_error "❌ .env file not found"
echo "Please create .env file with required variables"
exit 1
fi
log_success "✅ .env file exists"
# Load environment variables
source .env
# Check RPC endpoint
RPC_URL="${RPC_URL:-${RPC_URL_138:-${CHAIN138_RPC_URL:-}}}"
if [ -z "$RPC_URL" ]; then
log_error "❌ RPC_URL not set in .env"
echo "Please set RPC_URL in .env file"
log_error "❌ Chain 138 RPC not configured"
echo "Set RPC_URL, RPC_URL_138, or CHAIN138_RPC_URL."
else
log_success "✅ RPC_URL configured: ${RPC_URL}"
@@ -68,9 +57,9 @@ else
fi
# Check PRIVATE_KEY
if [ -z "$PRIVATE_KEY" ]; then
log_error "❌ PRIVATE_KEY not set in .env"
echo "Please set PRIVATE_KEY in .env file"
if ! require_private_key_env > /dev/null 2>&1; then
log_error "❌ PRIVATE_KEY not configured"
echo "Set PRIVATE_KEY in smom-dbis-138/.env, repo .env, or ~/.secure-secrets/private-keys.env."
else
log_success "✅ PRIVATE_KEY configured"
fi
@@ -110,28 +99,28 @@ else
fi
# WETH10
if [ -z "$WETH10_ADDRESS" ] || [ "$WETH10_ADDRESS" = "0x0000000000000000000000000000000000000000" ]; then
if [ -z "${WETH10_ADDRESS:-}" ] || [ "${WETH10_ADDRESS:-}" = "0x0000000000000000000000000000000000000000" ]; then
log_warn "⏳ WETH10_ADDRESS: Not deployed"
else
log_success "✅ WETH10_ADDRESS: ${WETH10_ADDRESS}"
fi
# CCIPWETH9Bridge
if [ -z "$CCIPWETH9BRIDGE_ADDRESS" ] || [ "$CCIPWETH9BRIDGE_ADDRESS" = "0x0000000000000000000000000000000000000000" ]; then
if [ -z "${CCIPWETH9BRIDGE_ADDRESS:-}" ] || [ "${CCIPWETH9BRIDGE_ADDRESS:-}" = "0x0000000000000000000000000000000000000000" ]; then
log_warn "⏳ CCIPWETH9BRIDGE_ADDRESS: Not deployed"
else
log_success "✅ CCIPWETH9BRIDGE_ADDRESS: ${CCIPWETH9BRIDGE_ADDRESS}"
fi
# CCIPWETH10Bridge
if [ -z "$CCIPWETH10BRIDGE_ADDRESS" ] || [ "$CCIPWETH10BRIDGE_ADDRESS" = "0x0000000000000000000000000000000000000000" ]; then
if [ -z "${CCIPWETH10BRIDGE_ADDRESS:-}" ] || [ "${CCIPWETH10BRIDGE_ADDRESS:-}" = "0x0000000000000000000000000000000000000000" ]; then
log_warn "⏳ CCIPWETH10BRIDGE_ADDRESS: Not deployed"
else
log_success "✅ CCIPWETH10BRIDGE_ADDRESS: ${CCIPWETH10BRIDGE_ADDRESS}"
fi
# Oracle Aggregator
if [ -z "$ORACLE_AGGREGATOR_ADDRESS" ] || [ "$ORACLE_AGGREGATOR_ADDRESS" = "0x0000000000000000000000000000000000000000" ]; then
if [ -z "${ORACLE_AGGREGATOR_ADDRESS:-}" ] || [ "${ORACLE_AGGREGATOR_ADDRESS:-}" = "0x0000000000000000000000000000000000000000" ]; then
log_warn "⏳ ORACLE_AGGREGATOR_ADDRESS: Not deployed"
else
log_success "✅ ORACLE_AGGREGATOR_ADDRESS: ${ORACLE_AGGREGATOR_ADDRESS}"
@@ -164,11 +153,10 @@ fi
# Summary
log_info "=== Summary ==="
echo "Ready for contract deployment:"
if [ -n "$RPC_URL" ] && [ -n "$PRIVATE_KEY" ]; then
if [ -n "${RPC_URL:-}" ] && [ -n "${PRIVATE_KEY:-}" ]; then
log_success "✅ Prerequisites met"
echo "Run: ./scripts/deployment/deploy-contracts-ordered.sh"
else
log_error "❌ Prerequisites not met"
echo "Please configure RPC_URL and PRIVATE_KEY in .env"
fi

View File

@@ -5,19 +5,17 @@
set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
if [[ -f "$SCRIPT_DIR/../lib/deployment/dotenv.sh" ]]; then
# shellcheck disable=SC1090
source "$SCRIPT_DIR/../lib/deployment/dotenv.sh"
load_deployment_env --repo-root "$PROJECT_ROOT"
fi
echo "=== Environment Variables Requirements Check ==="
echo ""
# Check if .env exists
if [ -f .env ]; then
echo "✓ .env file exists"
source .env
else
echo "✗ .env file not found"
echo " Create it from .env.template or manually"
echo ""
fi
echo ""
echo "=== Required Variables (Must Be Set) ==="
echo ""
@@ -32,7 +30,7 @@ REQUIRED_ALL=(
MISSING_REQUIRED=()
for var in "${REQUIRED_ALL[@]}"; do
if [ -z "${!var}" ] || [ "${!var}" == "0x..." ] || [ "${!var}" == "your_etherscan_api_key" ] || [ "${!var}" == "http://chain138.example.com:8545" ]; then
if [ -z "${!var:-}" ] || [ "${!var:-}" == "0x..." ] || [ "${!var:-}" == "your_etherscan_api_key" ] || [ "${!var:-}" == "http://chain138.example.com:8545" ]; then
echo "$var: NOT SET or using placeholder"
MISSING_REQUIRED+=("$var")
else
@@ -180,6 +178,13 @@ else
echo "Please set these variables in .env file before deployment"
fi
echo ""
echo "=== External Dependency Blockers ==="
echo ""
if ! bash "$PROJECT_ROOT/scripts/verify/check-external-dependencies.sh" --advisory; then
true
fi
echo ""
echo "=== Next Steps ==="
echo ""
@@ -194,4 +199,3 @@ else
fi
echo ""

View File

@@ -35,12 +35,8 @@ CRONOS_RPC="${CRONOS_RPC:-${CRONOS_RPC_URL:-https://evm.cronos.org}}"
CELO_RPC="${CELO_RPC:-${CELO_MAINNET_RPC:-https://forno.celo.org}}"
WEMIX_RPC="${WEMIX_RPC:-${WEMIX_MAINNET_RPC:-https://api.wemix.com}}"
if [[ -z "${PRIVATE_KEY:-}" ]]; then
echo "Error: Set PRIVATE_KEY in .env" >&2
exit 1
fi
DEPLOYER=$(cast wallet address "$PRIVATE_KEY" 2>/dev/null || true)
[[ -z "$DEPLOYER" ]] && { echo "Error: Could not derive deployer from PRIVATE_KEY" >&2; exit 1; }
DEPLOYER="${DEPLOYER_ADDRESS:-$(derive_deployer_address 2>/dev/null || true)}"
[[ -z "$DEPLOYER" ]] && { echo "Error: Set PRIVATE_KEY or DEPLOYER_ADDRESS in env before checking LINK balances." >&2; exit 1; }
RED='\033[0;31m'
GREEN='\033[0;32m'

View File

@@ -7,7 +7,6 @@ set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "$SCRIPT_DIR/../lib/init.sh"
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
# Load .env via dotenv (RPC CR/LF trim). Fallback: raw source.
if [[ -f "$SCRIPT_DIR/../lib/deployment/dotenv.sh" ]]; then
# shellcheck disable=SC1090
source "$SCRIPT_DIR/../lib/deployment/dotenv.sh"
@@ -24,15 +23,6 @@ elif [[ -n "${REPO_ROOT:-}" && -f "$REPO_ROOT/.env" ]]; then
set +a
fi
# Load environment variables
if [ -f "$PROJECT_ROOT/.env" ]; then
source "$PROJECT_ROOT/.env"
else
log_error "Error: .env file not found"
exit 1
fi
log_info "=== Mainnet Wallet Balance Check ==="
# Check if cast is available
@@ -42,20 +32,20 @@ if ! command -v cast &> /dev/null; then
fi
# Get wallet address from private key
if [ -z "$PRIVATE_KEY" ]; then
log_error "Error: PRIVATE_KEY not set in .env"
if ! require_private_key_env; then
exit 1
fi
WALLET_ADDRESS=$(cast wallet address --private-key "$PRIVATE_KEY" 2>/dev/null || echo "")
WALLET_ADDRESS="$(derive_deployer_address || true)"
if [ -z "$WALLET_ADDRESS" ]; then
log_error "Error: Could not derive address from private key"
log_error "ERROR: Could not derive DEPLOYER_ADDRESS from PRIVATE_KEY."
exit 1
fi
log_info "Wallet Address: $WALLET_ADDRESS"
# Mainnet RPC
MAINNET_RPC_URL="${MAINNET_RPC_URL:-${ETHEREUM_MAINNET_RPC:-}}"
if [ -z "$MAINNET_RPC_URL" ]; then
MAINNET_RPC_URL="https://eth.llamarpc.com"
fi
@@ -66,6 +56,7 @@ MAINNET_LINK="0x514910771AF9Ca656af840dff83E8264EcF986CA"
# Required amounts
ETH_RECOMMENDED="50000000000000000" # 0.05 ETH
LINK_RECOMMENDED="10000000000000000000" # 10 LINK
ETH_REQUIRED="${ETH_REQUIRED:-${MAINNET_ETH_REQUIRED:-25000000000000000}}" # 0.025 ETH
log_info "=== Mainnet ETH Balance ==="
@@ -94,6 +85,13 @@ log_info "=== Mainnet LINK Balance ==="
# Check LINK balance
LINK_BALANCE=$(cast call "$MAINNET_LINK" "balanceOf(address)(uint256)" "$WALLET_ADDRESS" --rpc-url "$MAINNET_RPC_URL" 2>/dev/null || echo "0")
if [[ "$LINK_BALANCE" =~ ^0x ]]; then
LINK_BALANCE=$(cast --to-dec "$LINK_BALANCE" 2>/dev/null || echo "0")
fi
LINK_BALANCE="$(printf '%s' "$LINK_BALANCE" | tr -d '\r\n[:space:]')"
if [[ -z "$LINK_BALANCE" || ! "$LINK_BALANCE" =~ ^[0-9]+$ ]]; then
LINK_BALANCE="0"
fi
if [ "$LINK_BALANCE" != "0" ] && [ -n "$LINK_BALANCE" ]; then
LINK_AMOUNT=$(cast --to-unit "$LINK_BALANCE" ether 2>/dev/null || echo "0")

View File

@@ -8,7 +8,6 @@ set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "$SCRIPT_DIR/../lib/init.sh"
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
# Load .env via dotenv (RPC CR/LF trim). Fallback: raw source.
if [[ -f "$SCRIPT_DIR/../lib/deployment/dotenv.sh" ]]; then
# shellcheck disable=SC1090
source "$SCRIPT_DIR/../lib/deployment/dotenv.sh"
@@ -25,18 +24,13 @@ elif [[ -n "${REPO_ROOT:-}" && -f "$REPO_ROOT/.env" ]]; then
set +a
fi
# Colors
# Load environment variables
if [ -f "$PROJECT_ROOT/.env" ]; then
source "$PROJECT_ROOT/.env"
else
log_error "Error: .env file not found"
exit 1
fi
log_info "=== Pre-Deployment Wallet Balance Check ==="
: "${MAINNET_ETH_REQUIRED:=25000000000000000}" # 0.025 ETH
: "${MAINNET_LINK_REQUIRED:=0}"
: "${CHAIN138_ETH_REQUIRED:=0}"
: "${CHAIN138_LINK_REQUIRED:=0}"
# Required amounts (in wei, then converted)
# Check if cast is available
@@ -46,14 +40,13 @@ if ! command -v cast &> /dev/null; then
fi
# Get wallet address from private key
if [ -z "$PRIVATE_KEY" ]; then
log_error "Error: PRIVATE_KEY not set in .env"
if ! require_private_key_env; then
exit 1
fi
WALLET_ADDRESS=$(cast wallet address --private-key "$PRIVATE_KEY" 2>/dev/null || echo "")
WALLET_ADDRESS="$(derive_deployer_address || true)"
if [ -z "$WALLET_ADDRESS" ]; then
log_error "Error: Could not derive address from PRIVATE_KEY"
log_error "ERROR: Could not derive DEPLOYER_ADDRESS from PRIVATE_KEY."
exit 1
fi
@@ -75,7 +68,15 @@ check_balance() {
# Check ERC20 token balance
balance=$(cast call "$token_address" "balanceOf(address)(uint256)" "$address" --rpc-url "$rpc_url" 2>/dev/null || echo "0")
fi
if [[ "$balance" =~ ^0x ]]; then
balance=$(cast --to-dec "$balance" 2>/dev/null || echo "0")
fi
balance="$(printf '%s' "$balance" | tr -d '\r\n[:space:]')"
if [[ -z "$balance" || ! "$balance" =~ ^[0-9]+$ ]]; then
balance="0"
fi
if [ "$balance" == "0" ] || [ -z "$balance" ]; then
balance="0"
fi
@@ -123,6 +124,7 @@ check_balance() {
# Check Mainnet balances
log_info "=== Ethereum Mainnet Balances ==="
MAINNET_RPC_URL="${MAINNET_RPC_URL:-${ETHEREUM_MAINNET_RPC:-}}"
if [ -z "$MAINNET_RPC_URL" ]; then
MAINNET_RPC_URL="https://eth.llamarpc.com"
log_warn "Using default Mainnet RPC: $MAINNET_RPC_URL"
@@ -150,8 +152,9 @@ fi
# Check ChainID 138 balances
log_info "=== ChainID 138 Balances ==="
RPC_URL="${RPC_URL:-${RPC_URL_138:-${CHAIN138_RPC_URL:-}}}"
if [ -z "$RPC_URL" ]; then
log_error "Error: RPC_URL not set in .env"
log_error "ERROR: Chain 138 RPC not available. Set RPC_URL, RPC_URL_138, or CHAIN138_RPC_URL."
exit 1
fi

View File

@@ -40,24 +40,19 @@ CRONOS_RPC="${CRONOS_RPC:-${CRONOS_RPC_URL:-https://evm.cronos.org}}"
CELO_RPC="${CELO_RPC:-${CELO_MAINNET_RPC:-https://forno.celo.org}}"
WEMIX_RPC="${WEMIX_RPC:-${WEMIX_MAINNET_RPC:-https://api.wemix.com}}"
PRIVATE_KEY="${PRIVATE_KEY:-}"
if [[ -n "$PRIVATE_KEY" && ! "$PRIVATE_KEY" =~ ^0x ]]; then
PRIVATE_KEY="0x$PRIVATE_KEY"
fi
# Chain 138 bridge addresses (required)
WETH9_138=$(grep "CCIPWETH9_BRIDGE_CHAIN138=" "$PROJECT_ROOT/.env" 2>/dev/null | cut -d'=' -f2 | tr -d ' "\r\n' || echo "${CCIPWETH9_BRIDGE_CHAIN138:-}")
WETH10_138=$(grep "CCIPWETH10_BRIDGE_CHAIN138=" "$PROJECT_ROOT/.env" 2>/dev/null | cut -d'=' -f2 | tr -d ' "\r\n' || echo "${CCIPWETH10_BRIDGE_CHAIN138:-}")
WETH9_138="${CCIPWETH9_BRIDGE_CHAIN138:-}"
WETH10_138="${CCIPWETH10_BRIDGE_CHAIN138:-}"
# Config-ready chain bridge addresses (optional; if set, we configure them)
WETH9_GNOSIS=$(grep "CCIPWETH9_BRIDGE_GNOSIS=" "$PROJECT_ROOT/.env" 2>/dev/null | cut -d'=' -f2 | tr -d ' "\r\n' || echo "${CCIPWETH9_BRIDGE_GNOSIS:-}")
WETH10_GNOSIS=$(grep "CCIPWETH10_BRIDGE_GNOSIS=" "$PROJECT_ROOT/.env" 2>/dev/null | cut -d'=' -f2 | tr -d ' "\r\n' || echo "${CCIPWETH10_BRIDGE_GNOSIS:-}")
WETH9_CRONOS=$(grep "CCIPWETH9_BRIDGE_CRONOS=" "$PROJECT_ROOT/.env" 2>/dev/null | cut -d'=' -f2 | tr -d ' "\r\n' || echo "${CCIPWETH9_BRIDGE_CRONOS:-}")
WETH10_CRONOS=$(grep "CCIPWETH10_BRIDGE_CRONOS=" "$PROJECT_ROOT/.env" 2>/dev/null | cut -d'=' -f2 | tr -d ' "\r\n' || echo "${CCIPWETH10_BRIDGE_CRONOS:-}")
WETH9_CELO=$(grep "CCIPWETH9_BRIDGE_CELO=" "$PROJECT_ROOT/.env" 2>/dev/null | cut -d'=' -f2 | tr -d ' "\r\n' || echo "${CCIPWETH9_BRIDGE_CELO:-}")
WETH10_CELO=$(grep "CCIPWETH10_BRIDGE_CELO=" "$PROJECT_ROOT/.env" 2>/dev/null | cut -d'=' -f2 | tr -d ' "\r\n' || echo "${CCIPWETH10_BRIDGE_CELO:-}")
WETH9_WEMIX=$(grep "CCIPWETH9_BRIDGE_WEMIX=" "$PROJECT_ROOT/.env" 2>/dev/null | cut -d'=' -f2 | tr -d ' "\r\n' || echo "${CCIPWETH9_BRIDGE_WEMIX:-}")
WETH10_WEMIX=$(grep "CCIPWETH10_BRIDGE_WEMIX=" "$PROJECT_ROOT/.env" 2>/dev/null | cut -d'=' -f2 | tr -d ' "\r\n' || echo "${CCIPWETH10_BRIDGE_WEMIX:-}")
WETH9_GNOSIS="${CCIPWETH9_BRIDGE_GNOSIS:-}"
WETH10_GNOSIS="${CCIPWETH10_BRIDGE_GNOSIS:-}"
WETH9_CRONOS="${CCIPWETH9_BRIDGE_CRONOS:-}"
WETH10_CRONOS="${CCIPWETH10_BRIDGE_CRONOS:-}"
WETH9_CELO="${CCIPWETH9_BRIDGE_CELO:-}"
WETH10_CELO="${CCIPWETH10_BRIDGE_CELO:-}"
WETH9_WEMIX="${CCIPWETH9_BRIDGE_WEMIX:-}"
WETH10_WEMIX="${CCIPWETH10_BRIDGE_WEMIX:-}"
# Besu eth_call via `cast call` can return -32602; lowercase 20-byte addresses avoids some failures on sends.
WETH9_138="${WETH9_138,,}"
@@ -72,8 +67,7 @@ if [[ -z "$WETH9_138" || -z "$WETH10_138" ]]; then
exit 1
fi
if [[ -z "$PRIVATE_KEY" ]]; then
echo "Error: PRIVATE_KEY not set in .env" >&2
if ! require_private_key_env; then
exit 1
fi

View File

@@ -59,7 +59,6 @@ _gas138="--legacy --gas-limit 250000 --gas-price 2000000000"
_gas_remote="--legacy --gas-limit 400000"
PRIVATE_KEY="${PRIVATE_KEY:-}"
[[ -n "$PRIVATE_KEY" && ! "$PRIVATE_KEY" =~ ^0x ]] && PRIVATE_KEY="0x$PRIVATE_KEY"
DRY_RUN="${DRY_RUN:-0}"
RED='\033[0;31m'
@@ -86,8 +85,7 @@ run_or_echo() {
echo -e "${GREEN}=== Avalanche/Arbitrum/Cronos ↔ Chain 138 Bridge Configuration ===${NC}"
echo ""
if [[ -z "$PRIVATE_KEY" ]]; then
echo -e "${RED}ERROR: PRIVATE_KEY not set${NC}"
if ! require_private_key_env; then
exit 1
fi

View File

@@ -0,0 +1,231 @@
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
if [[ -f "$REPO_ROOT/scripts/load-env.sh" ]]; then
# shellcheck disable=SC1090
source "$REPO_ROOT/scripts/load-env.sh" >/dev/null
elif [[ -f "$REPO_ROOT/../scripts/lib/load-project-env.sh" ]]; then
# shellcheck disable=SC1090
source "$REPO_ROOT/../scripts/lib/load-project-env.sh" >/dev/null
fi
APPLY=false
CHAIN_FILTER=""
while [[ $# -gt 0 ]]; do
case "$1" in
--apply) APPLY=true ;;
--chain=*) CHAIN_FILTER="${1#*=}" ;;
--chain)
CHAIN_FILTER="${2:-}"
shift
;;
*)
echo "Unknown arg: $1" >&2
echo "Usage: $0 [--apply] [--chain <chain_id>]" >&2
exit 1
;;
esac
shift
done
declare -A CHAIN_NAME=(
[1]="Ethereum Mainnet"
[10]="Optimism"
[25]="Cronos"
[56]="BSC"
[100]="Gnosis"
[137]="Polygon"
[8453]="Base"
[42161]="Arbitrum"
[42220]="Celo"
[43114]="Avalanche"
)
declare -A CHAIN_SELECTOR=(
[1]="5009297550715157269"
[10]="3734403246176062136"
[25]="1456215246176062136"
[56]="11344663589394136015"
[100]="465200170687744372"
[137]="4051577828743386545"
[8453]="15971525489660198786"
[42161]="4949039107694359620"
[42220]="1346049177634351622"
[43114]="6433500567565415381"
)
rpc_for_chain() {
case "$1" in
1) echo "${MAINNET_RPC_URL:-${ETHEREUM_MAINNET_RPC:-${ETHEREUM_RPC_URL:-}}}" ;;
10) echo "${OPTIMISM_RPC_URL:-${OPTIMISM_MAINNET_RPC:-}}" ;;
25) echo "${CRONOS_RPC_URL:-}" ;;
56) echo "${BSC_RPC_URL:-${BSC_MAINNET_RPC:-}}" ;;
100) echo "${GNOSIS_RPC_URL:-${GNOSIS_MAINNET_RPC:-${GNOSIS_RPC:-}}}" ;;
137) echo "${POLYGON_RPC_URL:-${POLYGON_MAINNET_RPC:-}}" ;;
8453) echo "${BASE_RPC_URL:-${BASE_MAINNET_RPC:-}}" ;;
42161) echo "${ARBITRUM_RPC_URL:-${ARBITRUM_MAINNET_RPC:-}}" ;;
42220) echo "${CELO_RPC_URL:-}" ;;
43114) echo "${AVALANCHE_RPC_URL:-${AVALANCHE_MAINNET_RPC:-}}" ;;
*) echo "" ;;
esac
}
bridge_for_chain() {
case "$1" in
1) echo "${CW_BRIDGE_MAINNET:-}" ;;
10) echo "${CW_BRIDGE_OPTIMISM:-}" ;;
25) echo "${CW_BRIDGE_CRONOS:-}" ;;
56) echo "${CW_BRIDGE_BSC:-}" ;;
100) echo "${CW_BRIDGE_GNOSIS:-}" ;;
137) echo "${CW_BRIDGE_POLYGON:-}" ;;
8453) echo "${CW_BRIDGE_BASE:-}" ;;
42161) echo "${CW_BRIDGE_ARBITRUM:-}" ;;
42220) echo "${CW_BRIDGE_CELO:-}" ;;
43114) echo "${CW_BRIDGE_AVALANCHE:-}" ;;
*) echo "" ;;
esac
}
probe_generation() {
local bridge="$1" rpc="$2"
if timeout 10 cast call "$bridge" 'sendRouter()(address)' --rpc-url "$rpc" >/dev/null 2>&1; then
echo "new"
return
fi
if timeout 10 cast call "$bridge" 'ccipRouter()(address)' --rpc-url "$rpc" >/dev/null 2>&1; then
echo "old"
return
fi
echo "unknown"
}
old_has_key() {
local bridge="$1" rpc="$2" key="$3"
local destinations
destinations="$(timeout 12 cast call "$bridge" 'getDestinationChains()(uint64[])' --rpc-url "$rpc" 2>/dev/null || true)"
[[ -z "$destinations" ]] && return 1
DESTINATIONS_PAYLOAD="$destinations" python3 - "$key" <<'PY'
import re
import os
import sys
target = sys.argv[1]
payload = os.environ.get("DESTINATIONS_PAYLOAD", "")
numbers = re.findall(r'\d+', payload)
sys.exit(0 if target in numbers else 1)
PY
}
new_read_destination() {
local bridge="$1" rpc="$2" key="$3"
timeout 12 cast call "$bridge" 'destinations(uint64)((address,bool))' "$key" --rpc-url "$rpc" 2>/dev/null || true
}
normalize_addr() {
printf '%s' "$1" | tr '[:upper:]' '[:lower:]'
}
new_matches() {
local raw="$1" expected="$2"
local lower_raw lower_expected
lower_raw="$(normalize_addr "$raw")"
lower_expected="$(normalize_addr "$expected")"
[[ "$lower_raw" == *"$lower_expected"* && "$lower_raw" == *"true"* ]]
}
send_tx() {
local rpc="$1" bridge="$2" signature="$3"
shift 3
cast send "$bridge" "$signature" "$@" --rpc-url "$rpc" --private-key "$PRIVATE_KEY"
}
configure_old() {
local source_chain="$1" target_key="$2" target_bridge="$3"
local rpc bridge op
rpc="$(rpc_for_chain "$source_chain")"
bridge="$(bridge_for_chain "$source_chain")"
if old_has_key "$bridge" "$rpc" "$target_key"; then
op="updateDestination(uint64,address)"
else
op="addDestination(uint64,address)"
fi
if [[ "$APPLY" == true ]]; then
send_tx "$rpc" "$bridge" "$op" "$target_key" "$target_bridge"
else
echo "DRY-RUN old chain=$source_chain key=$target_key bridge=$target_bridge op=$op"
fi
}
configure_new() {
local source_chain="$1" target_key="$2" target_bridge="$3"
local rpc bridge raw
rpc="$(rpc_for_chain "$source_chain")"
bridge="$(bridge_for_chain "$source_chain")"
raw="$(new_read_destination "$bridge" "$rpc" "$target_key")"
if new_matches "$raw" "$target_bridge"; then
echo "SKIP new chain=$source_chain key=$target_key already=$raw"
return
fi
if [[ "$APPLY" == true ]]; then
send_tx "$rpc" "$bridge" 'configureDestination(uint64,address,bool)' "$target_key" "$target_bridge" true
else
echo "DRY-RUN new chain=$source_chain key=$target_key bridge=$target_bridge"
fi
}
configure_key() {
local source_chain="$1" target_key="$2" target_bridge="$3" generation="$4"
case "$generation" in
old) configure_old "$source_chain" "$target_key" "$target_bridge" ;;
new) configure_new "$source_chain" "$target_key" "$target_bridge" ;;
*)
echo "ERROR unknown generation for chain $source_chain" >&2
return 1
;;
esac
}
echo "Public cW bridge mesh configuration"
echo "Apply mode: $APPLY"
echo
declare -A GENERATION=()
for chain_id in 1 10 25 56 100 137 8453 42161 42220 43114; do
[[ -n "$CHAIN_FILTER" && "$CHAIN_FILTER" != "$chain_id" ]] && continue
rpc="$(rpc_for_chain "$chain_id")"
bridge="$(bridge_for_chain "$chain_id")"
if [[ -z "$rpc" || -z "$bridge" ]]; then
echo "WARN chain=$chain_id missing rpc or bridge env; skipping"
continue
fi
generation="$(probe_generation "$bridge" "$rpc")"
GENERATION["$chain_id"]="$generation"
echo "chain=$chain_id name='${CHAIN_NAME[$chain_id]}' generation=$generation bridge=$bridge"
done
echo
for source_chain in 1 10 25 56 100 137 8453 42161 42220 43114; do
[[ -n "$CHAIN_FILTER" && "$CHAIN_FILTER" != "$source_chain" ]] && continue
[[ -z "${GENERATION[$source_chain]:-}" ]] && continue
for target_chain in 1 10 25 56 100 137 8453 42161 42220 43114; do
[[ "$source_chain" == "$target_chain" ]] && continue
target_bridge="$(bridge_for_chain "$target_chain")"
[[ -z "$target_bridge" ]] && continue
# Outbound path on source uses the target CCIP selector.
configure_key "$source_chain" "${CHAIN_SELECTOR[$target_chain]}" "$target_bridge" "${GENERATION[$source_chain]}"
# Inbound receive path on source uses the remote public chain id.
configure_key "$source_chain" "$target_chain" "$target_bridge" "${GENERATION[$source_chain]}"
done
done
echo
echo "Done."

View File

@@ -0,0 +1,81 @@
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
if [[ -f "$SCRIPT_DIR/../lib/deployment/dotenv.sh" ]]; then
# shellcheck disable=SC1090
source "$SCRIPT_DIR/../lib/deployment/dotenv.sh"
load_deployment_env --repo-root "$REPO_ROOT"
fi
FACTORY="${FACTORY:-${UNISWAP_V3_FACTORY:-}}"
RPC_URL="${RPC_URL:-}"
TOKEN_A="${TOKEN_A:-}"
TOKEN_B="${TOKEN_B:-}"
FEE="${FEE:-500}"
EXECUTE="${EXECUTE:-0}"
SQRT_PRICE_X96="${SQRT_PRICE_X96:-79228162514264337593543950336}"
PRIVATE_KEY="${PRIVATE_KEY:-}"
if [[ -z "$FACTORY" || -z "$RPC_URL" || -z "$TOKEN_A" || -z "$TOKEN_B" ]]; then
echo "Required: FACTORY RPC_URL TOKEN_A TOKEN_B" >&2
exit 1
fi
get_pool() {
cast call "$FACTORY" \
"getPool(address,address,uint24)(address)" \
"$TOKEN_A" "$TOKEN_B" "$FEE" \
--rpc-url "$RPC_URL" 2>/dev/null || true
}
pool="$(get_pool)"
pool="${pool//$'\n'/}"
if [[ -z "$pool" || "$pool" == "0x0000000000000000000000000000000000000000" ]]; then
echo "Pool does not exist yet for $TOKEN_A / $TOKEN_B fee $FEE"
if [[ "$EXECUTE" != "1" ]]; then
echo "Dry run: set EXECUTE=1 to call createPool on $FACTORY"
exit 0
fi
if [[ -z "$PRIVATE_KEY" ]]; then
echo "PRIVATE_KEY is required when EXECUTE=1" >&2
exit 1
fi
cast send "$FACTORY" \
"createPool(address,address,uint24)" \
"$TOKEN_A" "$TOKEN_B" "$FEE" \
--rpc-url "$RPC_URL" \
--private-key "$PRIVATE_KEY" \
-vv
pool="$(get_pool)"
pool="${pool//$'\n'/}"
fi
echo "Pool address: $pool"
slot0="$(cast call "$pool" "slot0()((uint160,int24,uint16,uint16,uint16,uint8,bool))" --rpc-url "$RPC_URL" 2>/dev/null || true)"
slot0_no_ws="$(printf '%s' "$slot0" | tr -d '[:space:]')"
if [[ "$slot0_no_ws" == "(0,0,0,0,0,0,false)" || -z "$slot0_no_ws" ]]; then
echo "Pool is not initialized"
if [[ "$EXECUTE" != "1" ]]; then
echo "Dry run: set EXECUTE=1 to initialize at sqrtPriceX96=$SQRT_PRICE_X96"
exit 0
fi
if [[ -z "$PRIVATE_KEY" ]]; then
echo "PRIVATE_KEY is required when EXECUTE=1" >&2
exit 1
fi
cast send "$pool" \
"initialize(uint160)" \
"$SQRT_PRICE_X96" \
--rpc-url "$RPC_URL" \
--private-key "$PRIVATE_KEY" \
-vv
echo "Initialized pool at sqrtPriceX96=$SQRT_PRICE_X96"
else
echo "Pool already initialized: $slot0"
fi

View File

@@ -0,0 +1,299 @@
#!/usr/bin/env bash
# Reconcile cW* token roles so only the configured per-network bridge keeps MINTER/BURNER.
#
# The script is intentionally conservative:
# - It grants MINTER/BURNER to the configured bridge if missing
# - It revokes MINTER/BURNER from the deployer/admin by default
# - It can revoke MINTER/BURNER from an explicit denylist of extra addresses
# - It can optionally freeze future operational role changes on newer cW* contracts
#
# Usage:
# bash scripts/deployment/cw-enforce-bridge-only-roles.sh --dry-run
# bash scripts/deployment/cw-enforce-bridge-only-roles.sh
#
# Optional env:
# CW_ROLE_CHAINS="1 10 25 56 100 137 42161 42220 43114 8453"
# CW_ROLE_TOKENS="CWUSDT CWUSDC CWEURC"
# CW_ROLE_EXTRA_REVOKE="0xabc...,0xdef..."
# CW_ROLE_KEEP_ALLOWLIST="0xabc...,0xdef..."
# CW_ROLE_REVOKE_DEPLOYER=1 # default 1
# CW_ROLE_FREEZE_IF_SUPPORTED=1 # default 1
# CW_ROLE_FROM_BLOCK=earliest # optional log scan start block for role event discovery
# PRIVATE_KEY # required unless --dry-run
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
SMOM_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
PROXMOX_ROOT="$(cd "$SMOM_ROOT/.." && pwd)"
cd "$SMOM_ROOT"
DRY_RUN=0
if [[ "${1:-}" == "--dry-run" ]]; then
DRY_RUN=1
fi
if [[ -f "$PROXMOX_ROOT/scripts/lib/load-project-env.sh" ]]; then
# shellcheck disable=SC1090
PROJECT_ROOT="$PROXMOX_ROOT" source "$PROXMOX_ROOT/scripts/lib/load-project-env.sh"
elif [[ -f .env ]]; then
set -a && source .env && set +a
fi
if ! command -v cast >/dev/null 2>&1; then
echo "cast is required" >&2
exit 1
fi
if ! command -v jq >/dev/null 2>&1; then
echo "jq is required" >&2
exit 1
fi
CHAIN_ROWS=(
"1|MAINNET|ETHEREUM_MAINNET_RPC|CW_BRIDGE_MAINNET"
"10|OPTIMISM|OPTIMISM_MAINNET_RPC|CW_BRIDGE_OPTIMISM"
"25|CRONOS|CRONOS_MAINNET_RPC|CW_BRIDGE_CRONOS"
"56|BSC|BSC_MAINNET_RPC|CW_BRIDGE_BSC"
"100|GNOSIS|GNOSIS_MAINNET_RPC|CW_BRIDGE_GNOSIS"
"137|POLYGON|POLYGON_MAINNET_RPC|CW_BRIDGE_POLYGON"
"42161|ARBITRUM|ARBITRUM_MAINNET_RPC|CW_BRIDGE_ARBITRUM"
"42220|CELO|CELO_MAINNET_RPC|CW_BRIDGE_CELO"
"43114|AVALANCHE|AVALANCHE_MAINNET_RPC|CW_BRIDGE_AVALANCHE"
"8453|BASE|BASE_MAINNET_RPC|CW_BRIDGE_BASE"
)
TOKENS=(
"CWUSDT"
"CWUSDC"
"CWAUSDT"
"CWUSDW"
"CWEURC"
"CWEURT"
"CWGBPC"
"CWGBPT"
"CWAUDC"
"CWJPYC"
"CWCHFC"
"CWCADC"
"CWXAUC"
"CWXAUT"
)
CHAIN_FILTER="${CW_ROLE_CHAINS:-1 10 25 56 100 137 42161 42220 43114 8453}"
TOKEN_FILTER="${CW_ROLE_TOKENS:-}"
REVOKE_DEPLOYER="${CW_ROLE_REVOKE_DEPLOYER:-1}"
FREEZE_IF_SUPPORTED="${CW_ROLE_FREEZE_IF_SUPPORTED:-1}"
EXTRA_REVOKE_RAW="${CW_ROLE_EXTRA_REVOKE:-}"
KEEP_ALLOWLIST_RAW="${CW_ROLE_KEEP_ALLOWLIST:-}"
ROLE_FROM_BLOCK="${CW_ROLE_FROM_BLOCK:-earliest}"
MINTER_ROLE="$(cast keccak "MINTER_ROLE")"
BURNER_ROLE="$(cast keccak "BURNER_ROLE")"
DEFAULT_ADMIN_ROLE="0x0000000000000000000000000000000000000000000000000000000000000000"
send_cmd() {
if [[ "$DRY_RUN" -eq 1 ]]; then
local rendered=()
local redact_next=0
local arg
for arg in "$@"; do
if [[ "$redact_next" -eq 1 ]]; then
rendered+=("<redacted>")
redact_next=0
continue
fi
if [[ "$arg" == "--private-key" ]]; then
rendered+=("$arg")
redact_next=1
continue
fi
rendered+=("$arg")
done
echo "[dry-run] ${rendered[*]}"
return 0
fi
"$@"
}
bool_call() {
local rpc="$1"
local contract="$2"
local sig="$3"
shift 3
cast call "$contract" "$sig" "$@" --rpc-url "$rpc" 2>/dev/null
}
normalize_address() {
local value="${1,,}"
value="${value#0x}"
if [[ ${#value} -lt 40 ]]; then
return 1
fi
echo "0x${value: -40}"
}
append_unique_address() {
local __var_name="$1"
local candidate="${2,,}"
[[ -n "$candidate" ]] || return 0
local current="${!__var_name:-}"
if [[ " $current " != *" $candidate "* ]]; then
printf -v "$__var_name" '%s%s ' "$current" "$candidate"
fi
}
load_csv_addresses() {
local raw="$1"
local __out_var="$2"
local item normalized
IFS=',' read -r -a _addr_items <<< "$raw"
for item in "${_addr_items[@]}"; do
item="${item//[[:space:]]/}"
[[ -n "$item" ]] || continue
normalized="$(normalize_address "$item" || true)"
[[ -n "$normalized" ]] || continue
append_unique_address "$__out_var" "$normalized"
done
}
discover_role_holders() {
local rpc="$1"
local token="$2"
local role="$3"
cast logs "RoleGranted(bytes32,address,address)" "$role" \
--from-block "$ROLE_FROM_BLOCK" --to-block latest --address "$token" --rpc-url "$rpc" --json 2>/dev/null |
jq -r '.[].topics[2] // empty' |
while read -r topic; do
normalize_address "$topic" || true
done
}
function_exists() {
local rpc="$1"
local contract="$2"
local sig="$3"
cast call "$contract" "$sig" --rpc-url "$rpc" >/dev/null 2>&1
}
grant_role_if_supported() {
local rpc="$1"
local token="$2"
local role="$3"
local target="$4"
local current
current="$(bool_call "$rpc" "$token" "hasRole(bytes32,address)(bool)" "$role" "$target" || true)"
if [[ "$current" != "true" ]]; then
send_cmd cast send "$token" "grantRole(bytes32,address)" "$role" "$target" \
--rpc-url "$rpc" --private-key "$PRIVATE_KEY"
fi
}
revoke_role_if_present() {
local rpc="$1"
local token="$2"
local role="$3"
local target="$4"
local current
current="$(bool_call "$rpc" "$token" "hasRole(bytes32,address)(bool)" "$role" "$target" || true)"
if [[ "$current" == "true" ]]; then
send_cmd cast send "$token" "revokeRole(bytes32,address)" "$role" "$target" \
--rpc-url "$rpc" --private-key "$PRIVATE_KEY"
fi
}
role_label() {
case "$1" in
"$DEFAULT_ADMIN_ROLE") echo "DEFAULT_ADMIN_ROLE" ;;
"$MINTER_ROLE") echo "MINTER_ROLE" ;;
"$BURNER_ROLE") echo "BURNER_ROLE" ;;
*) echo "$1" ;;
esac
}
for row in "${CHAIN_ROWS[@]}"; do
IFS='|' read -r chain_id chain_key rpc_var bridge_var <<< "$row"
if [[ " $CHAIN_FILTER " != *" $chain_id "* ]]; then
continue
fi
rpc="${!rpc_var:-}"
bridge="${!bridge_var:-}"
deployer="${DEPLOYER_ADDRESS:-${DEPLOYER_WALLET:-${DEPLOYER_EOA:-${DEFAULT_FROM_ADDRESS:-}}}}"
if [[ -z "$deployer" && -n "${PRIVATE_KEY:-}" ]]; then
deployer="$(cast wallet address --private-key "$PRIVATE_KEY" 2>/dev/null || true)"
fi
bridge="$(normalize_address "$bridge" || true)"
deployer="$(normalize_address "$deployer" || true)"
if [[ -z "$rpc" || -z "$bridge" ]]; then
echo "Skip chain $chain_id ($chain_key): missing rpc or bridge env"
continue
fi
echo "=== Chain $chain_id ($chain_key) ==="
echo "Bridge: $bridge"
for token_prefix in "${TOKENS[@]}"; do
if [[ -n "$TOKEN_FILTER" && " $TOKEN_FILTER " != *" $token_prefix "* ]]; then
continue
fi
token_var="${token_prefix}_${chain_key}"
token="${!token_var:-}"
if [[ -z "$token" || "$token" == "0x0000000000000000000000000000000000000000" ]]; then
continue
fi
echo "-- $token_var $token"
grant_role_if_supported "$rpc" "$token" "$MINTER_ROLE" "$bridge"
grant_role_if_supported "$rpc" "$token" "$BURNER_ROLE" "$bridge"
keep_allowlist=""
append_unique_address keep_allowlist "$bridge"
load_csv_addresses "$KEEP_ALLOWLIST_RAW" keep_allowlist
revoke_candidates=""
while read -r discovered; do
[[ -n "$discovered" ]] || continue
append_unique_address revoke_candidates "$discovered"
done < <(discover_role_holders "$rpc" "$token" "$MINTER_ROLE")
while read -r discovered; do
[[ -n "$discovered" ]] || continue
append_unique_address revoke_candidates "$discovered"
done < <(discover_role_holders "$rpc" "$token" "$BURNER_ROLE")
if [[ "$REVOKE_DEPLOYER" == "1" && -n "$deployer" ]]; then
append_unique_address revoke_candidates "$deployer"
fi
load_csv_addresses "$EXTRA_REVOKE_RAW" revoke_candidates
for addr in $revoke_candidates; do
[[ " $keep_allowlist " == *" ${addr,,} "* ]] && continue
revoke_role_if_present "$rpc" "$token" "$MINTER_ROLE" "$addr"
revoke_role_if_present "$rpc" "$token" "$BURNER_ROLE" "$addr"
done
if [[ "$FREEZE_IF_SUPPORTED" == "1" ]]; then
if function_exists "$rpc" "$token" "operationalRolesFrozen()(bool)"; then
frozen="$(cast call "$token" "operationalRolesFrozen()(bool)" --rpc-url "$rpc" 2>/dev/null || true)"
if [[ "$frozen" != "true" ]]; then
send_cmd cast send "$token" "freezeOperationalRoles()" \
--rpc-url "$rpc" --private-key "$PRIVATE_KEY" --gas-limit 80000
fi
fi
fi
for role in "$DEFAULT_ADMIN_ROLE" "$MINTER_ROLE" "$BURNER_ROLE"; do
summary_holders=""
while read -r discovered; do
[[ -n "$discovered" ]] || continue
current="$(bool_call "$rpc" "$token" "hasRole(bytes32,address)(bool)" "$role" "$discovered" || true)"
if [[ "$current" == "true" ]]; then
append_unique_address summary_holders "$discovered"
fi
done < <(discover_role_holders "$rpc" "$token" "$role")
echo " $(role_label "$role"): ${summary_holders:-<none discovered>}"
done
done
done

View File

@@ -27,16 +27,10 @@ RPC_URL="${RPC_URL_138:-http://localhost:8545}"
GAS_PRICE="${GAS_PRICE:-1000000000}"
PRIVATE_KEY="${PRIVATE_KEY:-}"
if [ -z "$PRIVATE_KEY" ]; then
echo "Error: PRIVATE_KEY environment variable not set"
if ! require_private_key_env; then
exit 1
fi
# Ensure PRIVATE_KEY has 0x prefix
if [[ ! "$PRIVATE_KEY" =~ ^0x ]]; then
export PRIVATE_KEY="0x$PRIVATE_KEY"
fi
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "Deploy All Contracts"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"

View File

@@ -29,18 +29,9 @@ echo "Phase 2 Complete Deployment from Proxy Host"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""
# Check if .env exists
if [ ! -f .env ]; then
echo "❌ Error: .env file not found"
echo "Please ensure .env is in $PROJECT_ROOT"
exit 1
fi
source .env
# Verify SSH key
if [ -z "$SSH_PRIVATE_KEY_PATH" ]; then
echo "❌ Error: SSH_PRIVATE_KEY_PATH not set in .env"
echo "ERROR: SSH_PRIVATE_KEY_PATH not available. Set it in smom-dbis-138/.env or repo .env." >&2
exit 1
fi

View File

@@ -25,15 +25,6 @@ elif [[ -n "${REPO_ROOT:-}" && -f "$REPO_ROOT/.env" ]]; then
set +a
fi
# Load environment variables
if [ -f "$PROJECT_ROOT/.env" ]; then
source "$PROJECT_ROOT/.env"
else
log_error "Error: .env file not found"
exit 1
fi
log_info "=== Ethereum Mainnet Deployment ==="
# Check wallet balances first (warning only)
@@ -55,7 +46,12 @@ if [[ "$MAINNET_RPC" == *"infura.io"* ]] && [[ -n "${INFURA_PROJECT_SECRET:-}" ]
_with_auth=$(build_infura_rpc "mainnet" 2>/dev/null || true)
[[ -n "$_with_auth" ]] && MAINNET_RPC="$_with_auth" && log_info "Using Infura Mainnet RPC with Basic Auth"
fi
MAINNET_PRIVATE_KEY="${PRIVATE_KEY}"
MAINNET_PRIVATE_KEY="${MAINNET_PRIVATE_KEY:-${PRIVATE_KEY:-}}"
export PRIVATE_KEY="$MAINNET_PRIVATE_KEY"
if ! require_private_key_env "Set MAINNET_PRIVATE_KEY or PRIVATE_KEY in smom-dbis-138/.env, repo .env, or ~/.secure-secrets/private-keys.env."; then
exit 1
fi
MAINNET_PRIVATE_KEY="$PRIVATE_KEY"
MAINNET_CCIP_ROUTER="${MAINNET_CCIP_ROUTER:-0x80226fc0Ee2b096224EeAc085Bb9a8cba1146f7D}" # Official Chainlink CCIP Router
MAINNET_LINK_TOKEN="${MAINNET_LINK_TOKEN:-0x514910771AF9Ca656af840dff83E8264EcF986CA}"
WETH9_ADDRESS="0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"

View File

@@ -30,36 +30,17 @@ fi
log_info "=== Deploy All Contracts in Proper Order ==="
# Check if .env exists
if [ ! -f .env ]; then
log_error "Error: .env file not found"
echo "Creating .env from .env.example..."
if [ -f .env.example ]; then
cp .env.example .env
log_success "✅ Created .env from .env.example"
log_warn "Please configure .env with your values before continuing"
exit 1
else
log_error "Error: .env.example not found"
exit 1
fi
fi
# Load environment variables
source .env
# Check PRIVATE_KEY
if [ -z "$PRIVATE_KEY" ]; then
log_error "Error: PRIVATE_KEY not set in .env"
if ! require_private_key_env; then
exit 1
fi
# Check RPC_URL
RPC_URL="${RPC_URL:-${RPC_URL_138:-${CHAIN138_RPC_URL:-}}}"
if [ -z "$RPC_URL" ]; then
log_warn "RPC_URL not set in .env"
log_warn "Chain 138 RPC not configured"
log_warn "Options:"
echo "1. Start local Anvil testnet (for testing)"
echo "2. Set RPC_URL to existing blockchain endpoint"
echo "2. Set RPC_URL/RPC_URL_138 to existing blockchain endpoint"
echo "3. Deploy blockchain infrastructure first (Azure/Kubernetes)"
read -p "Choose option (1/2/3): " choice
@@ -74,30 +55,12 @@ if [ -z "$RPC_URL" ]; then
# Set RPC_URL
RPC_URL="http://localhost:8545"
update_env() {
local key=$1
local value=$2
if grep -q "^${key}=" .env; then
sed -i "s|^${key}=.*|${key}=${value}|" .env
else
echo "${key}=${value}" >> .env
fi
}
update_env "RPC_URL" "$RPC_URL"
log_success "✅ RPC_URL set to: ${RPC_URL}"
log_warn "Note: Anvil is running in background. Kill it with: kill $ANVIL_PID"
;;
2)
read -p "Enter RPC URL: " RPC_URL
update_env() {
local key=$1
local value=$2
if grep -q "^${key}=" .env; then
sed -i "s|^${key}=.*|${key}=${value}|" .env
else
echo "${key}=${value}" >> .env
fi
}
update_env "RPC_URL" "$RPC_URL"
log_success "✅ RPC_URL set to: ${RPC_URL}"
;;
@@ -116,6 +79,17 @@ if [ -z "$RPC_URL" ]; then
esac
fi
update_env() {
local key=$1
local value=$2
if grep -q "^${key}=" .env 2>/dev/null; then
sed -i "s|^${key}=.*|${key}=${value}|" .env
else
echo "${key}=${value}" >> .env
fi
log_success "✅ Updated .env: ${key}=${value}"
}
# Verify RPC endpoint is accessible
log_warn "Verifying RPC endpoint..."
if ! curl -s -X POST "$RPC_URL" -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}' > /dev/null 2>&1; then
@@ -200,7 +174,7 @@ if [ -z "$CCIP_FEE_TOKEN" ] || [ "$CCIP_FEE_TOKEN" = "0x000000000000000000000000
# Fund deployer with LINK tokens
log_warn "Funding deployer with LINK tokens..."
DEPLOYER_ADDRESS=$(cast wallet address --private-key "$PRIVATE_KEY" 2>/dev/null || echo "")
DEPLOYER_ADDRESS="$(derive_deployer_address || true)"
if [ -n "$DEPLOYER_ADDRESS" ]; then
cast send "$CCIP_FEE_TOKEN" "mint(address,uint256)" "$DEPLOYER_ADDRESS" "1000000e18" \
--rpc-url "$RPC_URL" \

View File

@@ -32,12 +32,6 @@ elif [[ -n "${REPO_ROOT:-}" && -f "$REPO_ROOT/.env" ]]; then
set +a
fi
if [ -f .env ]; then
set -a
source .env
set +a
fi
RPC="${RPC_URL_138:-http://192.168.11.211:8545}"
GAS_PRICE="${GAS_PRICE_138:-1000000000}"
DRY_RUN=""
@@ -56,8 +50,7 @@ while [ $# -gt 0 ]; do
shift
done
if [ -z "${PRIVATE_KEY:-}" ]; then
echo "ERROR: PRIVATE_KEY not set in .env"
if ! require_private_key_env; then
exit 1
fi

View File

@@ -25,11 +25,6 @@ elif [[ -n "${REPO_ROOT:-}" && -f "$REPO_ROOT/.env" ]]; then
set +a
fi
# Source environment
if [ -f .env ]; then
source .env
fi
# Colors
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
@@ -43,8 +38,7 @@ echo -e "${BLUE}========================================${NC}"
echo ""
# Check prerequisites
if [ -z "${PRIVATE_KEY:-}" ]; then
echo -e "${RED}✗ PRIVATE_KEY not set in .env${NC}"
if ! require_private_key_env; then
exit 1
fi

View File

@@ -29,16 +29,6 @@ fi
DEPLOYMENT_LOG="${PROJECT_ROOT}/deployment.log"
CONTRACT_ADDRESSES_FILE="${PROJECT_ROOT}/contracts-deployed.json"
# Load environment variables
if [ -f "${PROJECT_ROOT}/.env" ]; then
set -a
source "${PROJECT_ROOT}/.env"
set +a
else
log_error "Error: .env file not found. Please create .env file from .env.example"
exit 1
fi
# Logging function
log() {
log_success "[$(date +'%Y-%m-%d %H:%M:%S')] $1" | tee -a "$DEPLOYMENT_LOG"
@@ -132,6 +122,12 @@ check_prerequisites() {
error "Required environment variable $var is not set"
fi
done
if [ "$SKIP_CONTRACTS" != "true" ]; then
export RPC_URL="${RPC_URL:-${RPC_URL_138:-${CHAIN138_RPC_URL:-}}}"
require_private_key_env || exit 1
[[ -n "${RPC_URL:-}" ]] || error "Required environment variable RPC_URL is not set"
fi
if [ $missing -eq 0 ]; then
log "All prerequisites met"

View File

@@ -41,6 +41,10 @@ SIMULATE="${SIMULATE:-0}"
PREFLIGHT="${PREFLIGHT:-1}"
CHAIN="${1:-all}"
if [[ "$DRY_RUN" != "1" && "$SIMULATE" != "1" ]] && ! require_private_key_env; then
exit 1
fi
# Per-chain env for DeployWETHBridges: RPC_URL, CCIP_ROUTER_ADDRESS, LINK_TOKEN_ADDRESS, WETH9_ADDRESS, WETH10_ADDRESS (0x0 to skip)
run_deploy() {
local chain_name="$1"
@@ -59,10 +63,6 @@ run_deploy() {
echo " Skip $chain_name: set ${rpc_var} and ${router_var} in .env"
return 0
fi
if [[ -z "${PRIVATE_KEY:-}" ]]; then
echo " Skip $chain_name: PRIVATE_KEY not set"
return 0
fi
if [[ -z "$weth9" || -z "$weth10" ]]; then
echo " Skip $chain_name: set ${weth9_var} and ${weth10_var} in .env (native wrapped token addresses from chain)"
return 0

View File

@@ -24,15 +24,6 @@ elif [[ -n "${REPO_ROOT:-}" && -f "$REPO_ROOT/.env" ]]; then
set +a
fi
# Load environment variables
if [ -f "$PROJECT_ROOT/.env" ]; then
source "$PROJECT_ROOT/.env"
else
log_error "Error: .env file not found"
exit 1
fi
# Canonical addresses
WETH9_ADDRESS="0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"
WETH10_ADDRESS="0xf4BB2e28688e89fCcE3c0580D37d36A7672E8A9f"
@@ -49,23 +40,21 @@ fi
log_success "✅ Wallet balances sufficient"
# Check required variables
MAINNET_RPC_URL="${MAINNET_RPC_URL:-${ETHEREUM_MAINNET_RPC:-}}"
if [ -z "$MAINNET_RPC_URL" ]; then
MAINNET_RPC_URL="https://eth.llamarpc.com"
log_warn "Using default Mainnet RPC: $MAINNET_RPC_URL"
fi
if [ -z "$MAINNET_PRIVATE_KEY" ]; then
if [ -n "$PRIVATE_KEY" ]; then
MAINNET_PRIVATE_KEY="$PRIVATE_KEY"
log_warn "Using PRIVATE_KEY for Mainnet deployment"
else
log_error "Error: MAINNET_PRIVATE_KEY or PRIVATE_KEY not set"
exit 1
fi
MAINNET_PRIVATE_KEY="${MAINNET_PRIVATE_KEY:-${PRIVATE_KEY:-}}"
export PRIVATE_KEY="$MAINNET_PRIVATE_KEY"
if ! require_private_key_env "Set MAINNET_PRIVATE_KEY or PRIVATE_KEY in smom-dbis-138/.env, repo .env, or ~/.secure-secrets/private-keys.env."; then
exit 1
fi
MAINNET_PRIVATE_KEY="$PRIVATE_KEY"
if [ -z "$MAINNET_CCIP_ROUTER" ]; then
log_error "Error: MAINNET_CCIP_ROUTER not set"
log_error "ERROR: MAINNET_CCIP_ROUTER not set"
exit 1
fi

View File

@@ -9,35 +9,13 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "$SCRIPT_DIR/../lib/init.sh"
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
# Load environment variables
if [ -f "$PROJECT_ROOT/.env" ]; then
source "$PROJECT_ROOT/.env"
fi
# Configuration
RPC_URL="${RPC_URL:-http://localhost:8545}"
RPC_URL="${RPC_URL:-}"
PRIVATE_KEY="${PRIVATE_KEY:-}"
FEE_TOKEN="${FEE_TOKEN:-}" # LINK token address
BASE_FEE="${BASE_FEE:-1000000000000000000}" # 1 LINK in wei
DATA_FEE_PER_BYTE="${DATA_FEE_PER_BYTE:-1000000000000000}" # 0.001 LINK per byte
if [ -z "$PRIVATE_KEY" ]; then
echo "Error: PRIVATE_KEY environment variable not set"
exit 1
fi
if [ -z "$FEE_TOKEN" ]; then
echo "Error: FEE_TOKEN environment variable not set (LINK token address)"
exit 1
fi
echo "Deploying CCIP Router..."
echo "RPC URL: $RPC_URL"
echo "Fee Token: $FEE_TOKEN"
echo "Base Fee: $BASE_FEE"
echo "Data Fee Per Byte: $DATA_FEE_PER_BYTE"
# Deploy using Foundry
cd "$PROJECT_ROOT"
# Load .env via dotenv (RPC CR/LF trim). Fallback: raw source.
if [[ -f "$SCRIPT_DIR/../lib/deployment/dotenv.sh" ]]; then
@@ -56,6 +34,22 @@ elif [[ -n "${REPO_ROOT:-}" && -f "$REPO_ROOT/.env" ]]; then
set +a
fi
RPC_URL="${RPC_URL:-${RPC_URL_138:-${CHAIN138_RPC_URL:-http://localhost:8545}}}"
if ! require_private_key_env; then
exit 1
fi
if [ -z "$FEE_TOKEN" ]; then
echo "ERROR: FEE_TOKEN not available. Set it in smom-dbis-138/.env or repo .env." >&2
exit 1
fi
echo "Deploying CCIP Router..."
echo "RPC URL: $RPC_URL"
echo "Fee Token: $FEE_TOKEN"
echo "Base Fee: $BASE_FEE"
echo "Data Fee Per Byte: $DATA_FEE_PER_BYTE"
forge script script/DeployCCIPRouter.s.sol \
--rpc-url "$RPC_URL" \
--broadcast \

View File

@@ -8,12 +8,7 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "$SCRIPT_DIR/../lib/init.sh"
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
# Load environment
if [ -f "$PROJECT_ROOT/.env" ]; then
source "$PROJECT_ROOT/.env"
fi
RPC_URL="${RPC_URL:-http://localhost:8545}"
RPC_URL="${RPC_URL:-}"
PRIVATE_KEY="${PRIVATE_KEY:-}"
# CCIP Configuration
@@ -23,29 +18,7 @@ CCIP_FEE_TOKEN="${CCIP_FEE_TOKEN:-}" # LINK token address
# WETH10 address (canonical Mainnet address or deployed address)
WETH10_ADDRESS="${WETH10_ADDRESS:-0xf4BB2e28688e89fCcE3c0580D37d36A7672E8A9F}"
if [ -z "$PRIVATE_KEY" ]; then
echo "Error: PRIVATE_KEY environment variable not set"
exit 1
fi
if [ -z "$CCIP_ROUTER" ]; then
echo "Error: CCIP_ROUTER environment variable not set"
exit 1
fi
if [ -z "$CCIP_FEE_TOKEN" ]; then
echo "Error: CCIP_FEE_TOKEN environment variable not set"
exit 1
fi
echo "Deploying CCIPWETH10Bridge to ChainID 138..."
echo "RPC URL: $RPC_URL"
echo "CCIP Router: $CCIP_ROUTER"
echo "WETH10 Address: $WETH10_ADDRESS"
echo "Fee Token (LINK): $CCIP_FEE_TOKEN"
cd "$PROJECT_ROOT"
# Load .env via dotenv (RPC CR/LF trim). Fallback: raw source.
if [[ -f "$SCRIPT_DIR/../lib/deployment/dotenv.sh" ]]; then
# shellcheck disable=SC1090
source "$SCRIPT_DIR/../lib/deployment/dotenv.sh"
@@ -62,6 +35,28 @@ elif [[ -n "${REPO_ROOT:-}" && -f "$REPO_ROOT/.env" ]]; then
set +a
fi
RPC_URL="${RPC_URL:-${RPC_URL_138:-${CHAIN138_RPC_URL:-http://localhost:8545}}}"
if ! require_private_key_env; then
exit 1
fi
if [ -z "$CCIP_ROUTER" ]; then
echo "ERROR: CCIP_ROUTER not available. Set it in smom-dbis-138/.env or repo .env." >&2
exit 1
fi
if [ -z "$CCIP_FEE_TOKEN" ]; then
echo "ERROR: CCIP_FEE_TOKEN not available. Set it in smom-dbis-138/.env or repo .env." >&2
exit 1
fi
echo "Deploying CCIPWETH10Bridge to ChainID 138..."
echo "RPC URL: $RPC_URL"
echo "CCIP Router: $CCIP_ROUTER"
echo "WETH10 Address: $WETH10_ADDRESS"
echo "Fee Token (LINK): $CCIP_FEE_TOKEN"
# Export environment variables for Foundry
export PRIVATE_KEY
export CCIP_ROUTER
@@ -79,4 +74,3 @@ echo "Next steps:"
echo "1. Save the deployed bridge address to your .env file"
echo "2. Configure bridge destinations:"
echo " ./scripts/deployment/configure-weth10-bridge.sh"

View File

@@ -8,12 +8,7 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "$SCRIPT_DIR/../lib/init.sh"
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
# Load environment
if [ -f "$PROJECT_ROOT/.env" ]; then
source "$PROJECT_ROOT/.env"
fi
RPC_URL="${RPC_URL:-http://localhost:8545}"
RPC_URL="${RPC_URL:-}"
PRIVATE_KEY="${PRIVATE_KEY:-}"
# CCIP Configuration
@@ -23,29 +18,7 @@ CCIP_FEE_TOKEN="${CCIP_FEE_TOKEN:-}" # LINK token address
# WETH9 address (canonical Mainnet address or deployed address)
WETH9_ADDRESS="${WETH9_ADDRESS:-0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2}"
if [ -z "$PRIVATE_KEY" ]; then
echo "Error: PRIVATE_KEY environment variable not set"
exit 1
fi
if [ -z "$CCIP_ROUTER" ]; then
echo "Error: CCIP_ROUTER environment variable not set"
exit 1
fi
if [ -z "$CCIP_FEE_TOKEN" ]; then
echo "Error: CCIP_FEE_TOKEN environment variable not set"
exit 1
fi
echo "Deploying CCIPWETH9Bridge to ChainID 138..."
echo "RPC URL: $RPC_URL"
echo "CCIP Router: $CCIP_ROUTER"
echo "WETH9 Address: $WETH9_ADDRESS"
echo "Fee Token (LINK): $CCIP_FEE_TOKEN"
cd "$PROJECT_ROOT"
# Load .env via dotenv (RPC CR/LF trim). Fallback: raw source.
if [[ -f "$SCRIPT_DIR/../lib/deployment/dotenv.sh" ]]; then
# shellcheck disable=SC1090
source "$SCRIPT_DIR/../lib/deployment/dotenv.sh"
@@ -62,6 +35,28 @@ elif [[ -n "${REPO_ROOT:-}" && -f "$REPO_ROOT/.env" ]]; then
set +a
fi
RPC_URL="${RPC_URL:-${RPC_URL_138:-${CHAIN138_RPC_URL:-http://localhost:8545}}}"
if ! require_private_key_env; then
exit 1
fi
if [ -z "$CCIP_ROUTER" ]; then
echo "ERROR: CCIP_ROUTER not available. Set it in smom-dbis-138/.env or repo .env." >&2
exit 1
fi
if [ -z "$CCIP_FEE_TOKEN" ]; then
echo "ERROR: CCIP_FEE_TOKEN not available. Set it in smom-dbis-138/.env or repo .env." >&2
exit 1
fi
echo "Deploying CCIPWETH9Bridge to ChainID 138..."
echo "RPC URL: $RPC_URL"
echo "CCIP Router: $CCIP_ROUTER"
echo "WETH9 Address: $WETH9_ADDRESS"
echo "Fee Token (LINK): $CCIP_FEE_TOKEN"
# Export environment variables for Foundry
export PRIVATE_KEY
export CCIP_ROUTER
@@ -79,4 +74,3 @@ echo "Next steps:"
echo "1. Save the deployed bridge address to your .env file"
echo "2. Configure bridge destinations:"
echo " ./scripts/deployment/configure-weth9-bridge.sh"

View File

@@ -0,0 +1,54 @@
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
if [[ -f "$SCRIPT_DIR/../lib/deployment/dotenv.sh" ]]; then
# shellcheck disable=SC1090
source "$SCRIPT_DIR/../lib/deployment/dotenv.sh"
load_deployment_env --repo-root "$REPO_ROOT"
fi
RPC_URL="${RPC_URL_138:-${CHAIN138_RPC:-http://192.168.11.211:8545}}"
PRIVATE_KEY="${PRIVATE_KEY:-}"
GAS_PRICE="${GAS_PRICE:-1000000000}"
EXECUTE="${EXECUTE:-0}"
ONLY_FAMILY="${GAS_FAMILY:-${1:-}}"
if [[ -z "$PRIVATE_KEY" ]]; then
echo "PRIVATE_KEY is required" >&2
exit 1
fi
cmd=(
bash scripts/forge/scope.sh script tokens
script/deploy/DeployGasCanonicalTokens.s.sol:DeployGasCanonicalTokens
--rpc-url "$RPC_URL"
--broadcast
--private-key "$PRIVATE_KEY"
--legacy
--with-gas-price "$GAS_PRICE"
-vvv
)
export FOUNDRY_PROFILE="${FOUNDRY_PROFILE:-chain138_legacy}"
if [[ -n "$ONLY_FAMILY" ]]; then
export GAS_FAMILY="$ONLY_FAMILY"
fi
echo "Chain 138 gas-canonical deploy"
echo " profile: $FOUNDRY_PROFILE"
echo " rpc: $RPC_URL"
echo " gas price: $GAS_PRICE"
echo " family: ${GAS_FAMILY:-all}"
if [[ "$EXECUTE" != "1" ]]; then
echo "Dry run only. Re-run with EXECUTE=1 to broadcast."
printf ' %q' "${cmd[@]}"
echo
exit 0
fi
cd "$REPO_ROOT"
"${cmd[@]}"

View File

@@ -30,22 +30,17 @@ RPC_URL="${RPC_URL:-http://localhost:8545}"
PRIVATE_KEY="${PRIVATE_KEY:-}"
NGINX_IP="${NGINX_PROXY_IP:-20.160.58.99}"
if [ -z "$PRIVATE_KEY" ]; then
echo "❌ Error: PRIVATE_KEY environment variable not set"
if ! require_private_key_env; then
exit 1
fi
# Ensure PRIVATE_KEY has 0x prefix
if [[ ! "$PRIVATE_KEY" =~ ^0x ]]; then
export PRIVATE_KEY="0x$PRIVATE_KEY"
fi
DEPLOYER="$(derive_deployer_address || echo 'unknown')"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "Deploy All Contracts - Network Ready"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""
echo "RPC URL: $RPC_URL"
echo "Deployer: $(cast wallet address --private-key "$PRIVATE_KEY" 2>/dev/null || echo 'unknown')"
echo "Deployer: $DEPLOYER"
echo ""
# Step 1: Verify network is ready

View File

@@ -28,23 +28,13 @@ elif [[ -n "${REPO_ROOT:-}" && -f "$REPO_ROOT/.env" ]]; then
set +a
fi
# Load environment variables
if [ ! -f .env ]; then
log_error "Error: .env file not found"
echo "Please create .env file with required variables"
exit 1
fi
source .env
# Check required variables
if [ -z "$PRIVATE_KEY" ]; then
log_error "Error: PRIVATE_KEY not set in .env"
if ! require_private_key_env; then
exit 1
fi
RPC_URL="${RPC_URL:-${RPC_URL_138:-${CHAIN138_RPC_URL:-}}}"
if [ -z "$RPC_URL" ]; then
log_error "Error: RPC_URL not set in .env"
log_error "ERROR: Chain 138 RPC not available. Set RPC_URL, RPC_URL_138, or CHAIN138_RPC_URL."
exit 1
fi
@@ -62,7 +52,7 @@ log_success "✅ RPC endpoint is accessible"
update_env() {
local key=$1
local value=$2
if grep -q "^${key}=" .env; then
if grep -q "^${key}=" .env 2>/dev/null; then
sed -i "s|^${key}=.*|${key}=${value}|" .env
else
echo "${key}=${value}" >> .env
@@ -240,7 +230,7 @@ ORACLE_HEARTBEAT=${ORACLE_HEARTBEAT:-"60"}
ORACLE_DEVIATION_THRESHOLD=${ORACLE_DEVIATION_THRESHOLD:-"50"}
# Get deployer address
DEPLOYER_ADDRESS=$(cast wallet address --private-key "$PRIVATE_KEY" 2>/dev/null || echo "")
DEPLOYER_ADDRESS="$(derive_deployer_address || true)"
if [ -z "$DEPLOYER_ADDRESS" ]; then
log_warn "Warning: Could not get deployer address. Using script default"

View File

@@ -25,23 +25,14 @@ elif [[ -n "${REPO_ROOT:-}" && -f "$REPO_ROOT/.env" ]]; then
set +a
fi
# Load environment variables
if [ ! -f .env ]; then
log_error "Error: .env file not found"
echo "Please create .env file with required variables"
exit 1
fi
source .env
# Check required variables
if [ -z "$PRIVATE_KEY" ]; then
log_error "Error: PRIVATE_KEY not set in .env"
if ! require_private_key_env; then
exit 1
fi
RPC_URL="${RPC_URL:-${RPC_URL_138:-${CHAIN138_RPC_URL:-}}}"
if [ -z "$RPC_URL" ]; then
log_error "Error: RPC_URL not set in .env"
log_error "ERROR: Chain 138 RPC not available. Set RPC_URL, RPC_URL_138, or CHAIN138_RPC_URL."
exit 1
fi
@@ -137,11 +128,8 @@ wait $MULTICALL_PID
wait $WETH9_PID
wait $WETH10_PID
# Reload .env to get deployed addresses
source .env
# Phase 2: CCIP Router (if needed)
if [ -z "$CCIP_ROUTER" ] || [ "$CCIP_ROUTER" = "0x0000000000000000000000000000000000000000" ]; then
if [ -z "${CCIP_ROUTER:-}" ] || [ "${CCIP_ROUTER:-}" = "0x0000000000000000000000000000000000000000" ]; then
log_warn "Phase 2: Deploying CCIP Router..."
CCIP_ROUTER_OUTPUT=$(forge_scoped script script/DeployCCIPRouter.s.sol:DeployCCIPRouter \
--sig "run(address,uint256,uint256)" \
@@ -161,11 +149,8 @@ else
log_success "✅ CCIP Router already configured: $CCIP_ROUTER"
fi
# Reload .env
source .env
# Phase 3: Bridge contracts (can be deployed in parallel if dependencies are met)
if [ "${DEPLOY_BRIDGES:-true}" = "true" ] && [ -n "$CCIP_ROUTER" ] && [ -n "$WETH9_ADDRESS" ] && [ -n "$WETH10_ADDRESS" ]; then
if [ "${DEPLOY_BRIDGES:-true}" = "true" ] && [ -n "${CCIP_ROUTER:-}" ] && [ -n "${WETH9_ADDRESS:-}" ] && [ -n "${WETH10_ADDRESS:-}" ]; then
log_warn "Phase 3: Deploying CCIP bridges in parallel..."
{
@@ -234,7 +219,7 @@ log_warn "Phase 4: Deploying Oracle and MultiSig in parallel..."
ORACLE_PID=$!
# Deploy MultiSig (independent if owners are set)
if [ -z "$MULTISIG_OWNERS" ]; then
if [ -z "${MULTISIG_OWNERS:-}" ]; then
log_warn "⚠️ MULTISIG_OWNERS not set. Skipping MultiSig deployment."
log_warn "Set MULTISIG_OWNERS in .env (comma-separated addresses) to deploy."
MULTISIG_PID=""
@@ -261,12 +246,8 @@ if [ -n "$MULTISIG_PID" ]; then
wait $MULTISIG_PID
fi
# Reload .env to get all new addresses
source .env
# Final summary
log_success "=== Deployment Summary ==="
source .env
log_success "Multicall: ${MULTICALL_ADDRESS:-N/A}"
log_success "WETH9: ${WETH9_ADDRESS:-N/A}"
log_success "WETH10: ${WETH10_ADDRESS:-N/A}"

View File

@@ -28,7 +28,7 @@ fi
# Default values
MODE="${MODE:-ordered}" # ordered or parallel
RPC_URL="${RPC_URL:-http://localhost:8545}"
RPC_URL="${RPC_URL:-}"
PRIVATE_KEY="${PRIVATE_KEY:-}"
DRY_RUN="${DRY_RUN:-false}"
@@ -81,24 +81,19 @@ while [[ $# -gt 0 ]]; do
usage
;;
*)
echo "Unknown option: $1"
echo "ERROR: Unknown option: $1" >&2
usage
;;
esac
done
if [ -z "$PRIVATE_KEY" ]; then
if [ -f .env ]; then
source .env
fi
if [ -z "$PRIVATE_KEY" ]; then
log_error "Error: PRIVATE_KEY not set. Use --private-key or set in .env"
exit 1
fi
if ! require_private_key_env "Use --private-key or set PRIVATE_KEY in smom-dbis-138/.env, repo .env, or ~/.secure-secrets/private-keys.env."; then
exit 1
fi
RPC_URL="${RPC_URL:-${RPC_URL_138:-${CHAIN138_RPC_URL:-}}}"
if [ -z "$RPC_URL" ]; then
log_error "Error: RPC_URL not set. Use --rpc-url or set in .env"
log_error "ERROR: Chain 138 RPC not available. Use --rpc-url or set RPC_URL/RPC_URL_138/CHAIN138_RPC_URL."
exit 1
fi

View File

@@ -8,46 +8,31 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../../" && pwd)"
cd "$PROJECT_ROOT"
# Load .env via dotenv (RPC CR/LF trim). Fallback: raw source.
if [[ -f "$SCRIPT_DIR/../lib/deployment/dotenv.sh" ]]; then
# shellcheck disable=SC1090
source "$SCRIPT_DIR/../lib/deployment/dotenv.sh"
load_deployment_env --repo-root "${PROJECT_ROOT:-$REPO_ROOT}"
elif [[ -n "${PROJECT_ROOT:-}" && -f "$PROJECT_ROOT/.env" ]]; then
elif [[ -f "$PROJECT_ROOT/.env" ]]; then
set -a
# shellcheck disable=SC1090
source "$PROJECT_ROOT/.env"
set +a
elif [[ -n "${REPO_ROOT:-}" && -f "$REPO_ROOT/.env" ]]; then
set -a
# shellcheck disable=SC1090
source "$REPO_ROOT/.env"
set +a
fi
# Source environment variables
if [ -f .env ]; then
source .env
else
echo "❌ Error: .env file not found"
exit 1
fi
# Check required variables
if [ -z "$PRIVATE_KEY" ]; then
echo "❌ Error: PRIVATE_KEY not set in .env"
if ! require_private_key_env; then
exit 1
fi
if [ -z "$ETHEREUM_MAINNET_RPC" ]; then
echo "❌ Error: ETHEREUM_MAINNET_RPC not set in .env"
echo "ERROR: ETHEREUM_MAINNET_RPC not available. Set it in smom-dbis-138/.env or repo .env." >&2
exit 1
fi
# Get deployer address
DEPLOYER=$(cast wallet address $PRIVATE_KEY 2>/dev/null || echo "")
DEPLOYER="$(derive_deployer_address || true)"
if [ -z "$DEPLOYER" ]; then
echo "❌ Error: Could not derive deployer address from PRIVATE_KEY"
echo "ERROR: Could not derive DEPLOYER_ADDRESS from PRIVATE_KEY." >&2
exit 1
fi
@@ -87,7 +72,7 @@ echo "=== Deploying MainnetTether ==="
LOG_FILE="/tmp/mainnet_tether_deploy.log"
forge script script/DeployMainnetTether.s.sol \
--rpc-url "$ETHEREUM_MAINNET_RPC" \
--private-key $PRIVATE_KEY \
--private-key "$PRIVATE_KEY" \
--broadcast \
--verify \
--via-ir \
@@ -121,7 +106,7 @@ echo "=== Deploying TransactionMirror ==="
LOG_FILE="/tmp/transaction_mirror_deploy.log"
forge script script/DeployTransactionMirror.s.sol \
--rpc-url "$ETHEREUM_MAINNET_RPC" \
--private-key $PRIVATE_KEY \
--private-key "$PRIVATE_KEY" \
--broadcast \
--verify \
--via-ir \
@@ -171,4 +156,3 @@ if [ -n "$MAINNET_TETHER" ] && [ -n "$TRANSACTION_MIRROR" ]; then
else
echo "⚠️ Deployment incomplete - check logs for details"
fi

View File

@@ -8,17 +8,6 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "$SCRIPT_DIR/../lib/init.sh"
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
RPC_URL="${RPC_URL:-http://localhost:8545}"
PRIVATE_KEY="${PRIVATE_KEY:-}"
if [ -z "$PRIVATE_KEY" ]; then
echo "Error: PRIVATE_KEY environment variable not set"
exit 1
fi
echo "Deploying Multicall to ChainID 138..."
echo "RPC URL: $RPC_URL"
cd "$PROJECT_ROOT"
# Load .env via dotenv (RPC CR/LF trim). Fallback: raw source.
if [[ -f "$SCRIPT_DIR/../lib/deployment/dotenv.sh" ]]; then
@@ -37,6 +26,14 @@ elif [[ -n "${REPO_ROOT:-}" && -f "$REPO_ROOT/.env" ]]; then
set +a
fi
RPC_URL="${RPC_URL:-${RPC_URL_138:-${CHAIN138_RPC_URL:-http://localhost:8545}}}"
if ! require_private_key_env; then
exit 1
fi
echo "Deploying Multicall to ChainID 138..."
echo "RPC URL: $RPC_URL"
forge script script/DeployMulticall.s.sol \
--rpc-url "$RPC_URL" \
--broadcast \
@@ -44,4 +41,3 @@ forge script script/DeployMulticall.s.sol \
--verify
echo "Multicall deployment complete!"

View File

@@ -29,13 +29,12 @@ RPC_URL="${RPC_URL:-http://localhost:8545}"
PRIVATE_KEY="${PRIVATE_KEY:-}"
OWNERS="${OWNERS:-}" # Comma-separated list of owner addresses
if [ -z "$PRIVATE_KEY" ]; then
echo "Error: PRIVATE_KEY not set"
if ! require_private_key_env; then
exit 1
fi
if [ -z "$OWNERS" ]; then
echo "Error: OWNERS not set (comma-separated list)"
echo "ERROR: OWNERS not set. Provide a comma-separated owner list." >&2
exit 1
fi
@@ -59,4 +58,3 @@ echo "Next steps:"
echo "1. Transfer admin roles to multi-sig address"
echo "2. Test multi-sig operations"
echo "3. Document multi-sig procedures"

View File

@@ -29,12 +29,6 @@ elif [[ -n "${REPO_ROOT:-}" && -f "$REPO_ROOT/.env" ]]; then
set +a
fi
if [ -f .env ]; then
set -a
source .env
set +a
fi
RPC="${RPC_URL_138:-http://192.168.11.211:8545}"
# Default 2 gwei to reduce "Replacement transaction underpriced"; override with GAS_PRICE_138.
GAS_PRICE="${GAS_PRICE_138:-2000000000}"
@@ -52,8 +46,7 @@ while [ $# -gt 0 ]; do
shift
done
if [ -z "${PRIVATE_KEY:-}" ]; then
echo "ERROR: PRIVATE_KEY not set in .env"
if ! require_private_key_env; then
exit 1
fi

View File

@@ -25,14 +25,7 @@ elif [[ -n "${REPO_ROOT:-}" && -f "$REPO_ROOT/.env" ]]; then
set +a
fi
# Load environment variables
if [ ! -f .env ]; then
log_error "Error: .env file not found"
echo "Please create .env file with required variables"
exit 1
fi
source .env
RPC_URL="${RPC_URL:-${RPC_URL_138:-${CHAIN138_RPC_URL:-}}}"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "Phase 2 + Contract Deployment - Full Parallel Mode"
@@ -123,7 +116,6 @@ PHASE2_VERIFY_PID=$!
if [ -n "$CONTRACTS_PID" ] && [ -n "$RPC_URL" ]; then
(
log_warn "Verifying contracts..."
source .env
if ./scripts/deployment/verify-contracts-parallel.sh > /tmp/contracts-verify.out 2>&1; then
log_success "✅ Contracts verified"
else
@@ -166,4 +158,3 @@ echo "Next steps:"
echo "1. Review Phase 2 services: ./terraform/phases/phase2/scripts/status.sh all"
echo "2. Verify contracts: ./scripts/deployment/verify-contracts-parallel.sh"
echo "3. Check deployment outputs: terraform/phases/phase2/terraform output"

View File

@@ -31,15 +31,6 @@ echo "Phase 2 Deployment from Proxy Host"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""
# Check if .env exists
if [ ! -f .env ]; then
echo "Error: .env file not found"
echo "Please ensure .env is available on this host"
exit 1
fi
source .env
# Change to Phase 2 directory
cd terraform/phases/phase2
@@ -64,4 +55,3 @@ echo ""
echo "Next steps:"
echo "1. Start services: ./terraform/phases/phase2/scripts/start-services.sh all"
echo "2. Check status: ./terraform/phases/phase2/scripts/status.sh all"

View File

@@ -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" \

View File

@@ -27,21 +27,13 @@ elif [[ -n "${REPO_ROOT:-}" && -f "$REPO_ROOT/.env" ]]; then
set +a
fi
# Load .env
if [ -f .env ]; then
set -a
source .env
set +a
fi
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
RED='\033[0;31m'
BLUE='\033[0;34m'
NC='\033[0m'
if [ -z "${PRIVATE_KEY:-}" ]; then
echo -e "${RED}ERROR: PRIVATE_KEY not set in .env${NC}"
if ! require_private_key_env; then
exit 1
fi

Some files were not shown because too many files have changed in this diff Show More