Compare commits
7 Commits
devin/ci-h
...
feature/to
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fcd55aa9c4 | ||
|
|
c3b1b2cebc | ||
|
|
4540ec4480 | ||
|
|
045a6a9245 | ||
|
|
f3d2961b97 | ||
|
|
c3d4c786fa | ||
|
|
768168de5e |
71
.github/workflows/hybx-omnl-ts.yml
vendored
Normal file
71
.github/workflows/hybx-omnl-ts.yml
vendored
Normal 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
67
.github/workflows/omnl-reconcile.yml
vendored
Normal 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
1
.gitignore
vendored
@@ -29,6 +29,7 @@ datadir/
|
||||
# Foundry
|
||||
out/
|
||||
artifacts/
|
||||
.omnl-poller-state.json
|
||||
cache/
|
||||
broadcast/
|
||||
.cronos-verify/
|
||||
|
||||
@@ -578,6 +578,7 @@ See [ALL Mainnet Master Documentation](../docs/MASTER_INDEX.md) for complete int
|
||||
- [Financial Tokenization (Archived)](docs/archive/status-reports/operations-legacy/FINANCIAL_TOKENIZATION.md) - Historical ISO-20022 and SWIFT FIN tokenization notes
|
||||
- [ALL Mainnet Integration](../docs/MASTER_INDEX.md) - Complete ALL Mainnet (651940) integration guide
|
||||
- [Multi-Chain Deployment](docs/deployment/MULTI_CHAIN_DEPLOYMENT_GUIDE.md) - Multi-chain deployment guide
|
||||
- [HYBX OMNL (reserves, compliance, CCIP mirror)](docs/hybx-omnl/README.md) - Policy, deployment, IPSAS anchor, token-aggregation routes
|
||||
|
||||
### 📊 Operations & Runbooks
|
||||
|
||||
|
||||
54
config/chain138-eth-pmm-liquidity-plan.json
Normal file
54
config/chain138-eth-pmm-liquidity-plan.json
Normal 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"
|
||||
}
|
||||
}
|
||||
142
config/chain138-eth-pmm-pool-deployment-map.json
Normal file
142
config/chain138-eth-pmm-pool-deployment-map.json
Normal 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"
|
||||
}
|
||||
]
|
||||
}
|
||||
67
config/chain138-eth-pmm-pools-execution.json
Normal file
67
config/chain138-eth-pmm-pools-execution.json
Normal 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": []
|
||||
}
|
||||
@@ -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": []
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
47
config/config-rpc-alltra-hybx.toml
Normal file
47
config/config-rpc-alltra-hybx.toml
Normal 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
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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=[]
|
||||
|
||||
49
config/config-sentry-promoted.toml
Normal file
49
config/config-sentry-promoted.toml
Normal 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
|
||||
@@ -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
|
||||
|
||||
|
||||
40
config/deployment-omnl.example.env
Normal file
40
config/deployment-omnl.example.env
Normal 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=
|
||||
548
config/gas-pmm-execution-bundle.json
Normal file
548
config/gas-pmm-execution-bundle.json
Normal 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"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
20
config/hybx-omnl-cross-chain-lines.example.json
Normal file
20
config/hybx-omnl-cross-chain-lines.example.json
Normal 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"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
4
config/hybx-omnl-cross-chain-lines.json
Normal file
4
config/hybx-omnl-cross-chain-lines.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"version": "1.0.0",
|
||||
"lines": []
|
||||
}
|
||||
22
config/hybx-omnl-policy.json
Normal file
22
config/hybx-omnl-policy.json
Normal 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
|
||||
}
|
||||
}
|
||||
60
config/omnl-ipsas-gl-registry.json
Normal file
60
config/omnl-ipsas-gl-registry.json
Normal 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)" }
|
||||
}
|
||||
}
|
||||
99
config/omnl-journal-matrix.json
Normal file
99
config/omnl-journal-matrix.json
Normal file
@@ -0,0 +1,99 @@
|
||||
{
|
||||
"description": "OMNL journal entries for API posting to OMNL Hybx (Fineract). Head Office and entities 2–8 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"
|
||||
}
|
||||
]
|
||||
}
|
||||
97
config/optimism-cronos-dodo-4-pools-execution-bundle.json
Normal file
97
config/optimism-cronos-dodo-4-pools-execution-bundle.json
Normal 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"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
162
contracts/hybx-omnl/ComplianceCore.sol
Normal file
162
contracts/hybx-omnl/ComplianceCore.sol
Normal 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
|
||||
);
|
||||
}
|
||||
}
|
||||
92
contracts/hybx-omnl/InstrumentRegistry.sol
Normal file
92
contracts/hybx-omnl/InstrumentRegistry.sol
Normal 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];
|
||||
}
|
||||
}
|
||||
53
contracts/hybx-omnl/OMNLCircuitBreaker.sol
Normal file
53
contracts/hybx-omnl/OMNLCircuitBreaker.sol
Normal 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;
|
||||
}
|
||||
}
|
||||
109
contracts/hybx-omnl/OMNLMirrorCoordinator.sol
Normal file
109
contracts/hybx-omnl/OMNLMirrorCoordinator.sol
Normal 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);
|
||||
}
|
||||
}
|
||||
80
contracts/hybx-omnl/OMNLMirrorReceiver.sol
Normal file
80
contracts/hybx-omnl/OMNLMirrorReceiver.sol
Normal 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);
|
||||
}
|
||||
}
|
||||
52
contracts/hybx-omnl/PolicyMath.sol
Normal file
52
contracts/hybx-omnl/PolicyMath.sol
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
175
contracts/hybx-omnl/ReserveCommitmentStore.sol
Normal file
175
contracts/hybx-omnl/ReserveCommitmentStore.sol
Normal 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];
|
||||
}
|
||||
}
|
||||
12
contracts/hybx-omnl/interfaces/IZkReserveProofVerifier.sol
Normal file
12
contracts/hybx-omnl/interfaces/IZkReserveProofVerifier.sol
Normal 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);
|
||||
}
|
||||
64
contracts/universal-resource/PolicyProfileRegistry.sol
Normal file
64
contracts/universal-resource/PolicyProfileRegistry.sol
Normal 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)];
|
||||
}
|
||||
}
|
||||
@@ -53,9 +53,9 @@ Per [Contract Deployment and Verification](https://docs.cronos.org/for-dapp-deve
|
||||
- Requires API key in Hardhat config
|
||||
|
||||
### 3. Foundry
|
||||
- `forge verify-contract --verifier blockscout` fails (Cronos Explorer API format differs)
|
||||
- Run `./scripts/deployment/verify-cronos-contracts.sh` to attempt; falls back to manual verification
|
||||
- **Recommended**: Manual verification via the web interface above
|
||||
- `forge verify-contract` does not successfully submit verification to Cronos Explorer (legacy Etherscan-style POST returns 404; Etherscan v2 does not support chain id 25).
|
||||
- Run `./scripts/deployment/verify-cronos-contracts.sh` to confirm bytecode on RPC and print manual steps (optional `CRONOS_EXPORT_SOURCES=1` to regenerate Standard-JSON).
|
||||
- **Recommended**: Manual verification via the web interface above and `docs/deployment/CRONOS_VERIFICATION_RUNBOOK.md`
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -66,6 +66,16 @@ This is the **master index** of all project documentation. Use this as your star
|
||||
- **[36-REGION-BLUEPRINT.md](deployment/36-REGION-BLUEPRINT.md)** - 36-region deployment blueprint
|
||||
- **[DEPLOYMENT_CONFIGURATION_AUDIT.md](deployment/DEPLOYMENT_CONFIGURATION_AUDIT.md)** - Configuration audit
|
||||
|
||||
### HYBX OMNL (reserves, compliance, CCIP mirror)
|
||||
|
||||
- **[DEPLOYMENT_CHECKLIST.md](hybx-omnl/DEPLOYMENT_CHECKLIST.md)** — OMNL deploy and token-aggregation checklist
|
||||
- **[OMNL_RECONCILE_CRON_AND_CI.md](hybx-omnl/OMNL_RECONCILE_CRON_AND_CI.md)** — IPSAS/journal anchor: cron, artifact script, GitHub Actions
|
||||
- **[OPERATIONAL_COMPLIANCE.md](hybx-omnl/OPERATIONAL_COMPLIANCE.md)** — Webhooks, retention, reconciliation anchors
|
||||
- **[OMNL_IPSAS_API.md](hybx-omnl/OMNL_IPSAS_API.md)** — IPSAS / GL API surface
|
||||
- **[CCIP_MIRROR_FLOW.md](hybx-omnl/CCIP_MIRROR_FLOW.md)** — CCIP mirror flow
|
||||
- **[HYBX_OMNL_POLICY_SPEC.md](hybx-omnl/HYBX_OMNL_POLICY_SPEC.md)** — Policy parameters (M0/M1 rules)
|
||||
- **[EXTERNAL_AUDIT_CHECKLIST.md](hybx-omnl/EXTERNAL_AUDIT_CHECKLIST.md)** — Third-party audit scope
|
||||
|
||||
---
|
||||
|
||||
## 🔧 Operations
|
||||
|
||||
@@ -37,7 +37,7 @@ besu.d-bis.org. 1 IN A 70.153.83.83 ; cf_tags=cf-proxied:true
|
||||
blockscout.d-bis.org. 1 IN A 20.215.32.42 ; cf_tags=cf-proxied:true
|
||||
blockscout.d-bis.org. 1 IN A 70.153.83.83 ; cf_tags=cf-proxied:true
|
||||
d-bis.org. 1 IN A 20.215.32.42 ; cf_tags=cf-proxied:true
|
||||
d-bis.org. 1 IN A 20.215.32.15 ; Digital Bank of International Settlements - Defi Oracle Meta Mainnet cf_tags=cf-proxied:true
|
||||
d-bis.org. 1 IN A 20.215.32.15 ; Digital Bank of International Settlements - DeFi Oracle Meta Mainnet cf_tags=cf-proxied:true
|
||||
docs.d-bis.org. 1 IN A 20.8.47.226 ; cf_tags=cf-proxied:false
|
||||
explorer.d-bis.org. 1 IN A 20.215.32.42 ; cf_tags=cf-proxied:true
|
||||
explorer.d-bis.org. 1 IN A 70.153.83.83 ; cf_tags=cf-proxied:true
|
||||
|
||||
@@ -72,10 +72,16 @@ Repeat for each destination chain (BSC, Polygon, Base, Optimism, Avalanche, Cron
|
||||
|
||||
---
|
||||
|
||||
## 4. LINK Funding
|
||||
## 4. LINK Funding and Activation Scope
|
||||
|
||||
- CCIP charges fees for each cross-chain message; bridges typically pay in **LINK** (or native) depending on the contract’s `feeToken` and `ccipSend` usage.
|
||||
- **Per chain**: Ensure the **bridge contract** holds enough **LINK** (and native for gas) so it can pay CCIP fees when users call `sendCrossChain` (or equivalent).
|
||||
- Important distinction:
|
||||
- **Destination-only receipt** on a chain does not by itself require LINK on that chain's bridge contract, because `ccipReceive(...)` does not pay the CCIP fee.
|
||||
- **Outbound sending** from a chain does require a fee payment path on that chain's bridge contract.
|
||||
- Current WEMIX nuance:
|
||||
- `CCIPWETH9Bridge` may be configured for native-fee mode by setting `feeToken = address(0)`.
|
||||
- `CCIPWETH10Bridge` still requires LINK in the current implementation.
|
||||
- **How to fund**:
|
||||
1. Get LINK on the same chain as the bridge (e.g. from an exchange or faucet).
|
||||
2. Transfer LINK to the **bridge contract address** (the same address you use for `addDestination`), or use any approved mechanism (e.g. admin top-up function if the contract has one).
|
||||
|
||||
@@ -84,6 +84,12 @@ This index helps you find the right deployment guide for your needs.
|
||||
- [Cloud for Sovereignty Landing Zone](CLOUD_FOR_SOVEREIGNTY_LANDING_ZONE.md) - Cloud for sovereignty landing zone
|
||||
- [Deployment Firefly Cacti](DEPLOYMENT_FIREFLY_CACTI.md) - Firefly and Cacti deployment
|
||||
|
||||
### HYBX OMNL (reserve commitments, compliance, CCIP mirror, IPSAS GL)
|
||||
- [HYBX OMNL documentation index](../hybx-omnl/README.md) - All OMNL guides in one place
|
||||
- [OMNL deployment checklist](../hybx-omnl/DEPLOYMENT_CHECKLIST.md) - Contracts, env, API, verification
|
||||
- [OMNL reconcile (cron & CI)](../hybx-omnl/OMNL_RECONCILE_CRON_AND_CI.md) - Anchor hash artifacts and automation
|
||||
- [OMNL IPSAS / GL API](../hybx-omnl/OMNL_IPSAS_API.md) - Reporting API surface
|
||||
|
||||
## Historical/Status Reports
|
||||
|
||||
These are historical deployment reports and status documents. Consider archiving if older than 6 months:
|
||||
|
||||
@@ -18,40 +18,50 @@ PRIVATE_KEY=0x0000000000000000000000000000000000000000000000000000000000000000
|
||||
# =============================================================================
|
||||
# RPC Endpoints
|
||||
# =============================================================================
|
||||
# Infura project (used for supported public chains)
|
||||
INFURA_PROJECT_ID=43b945b33d58463a9246cf5ca8aa6286
|
||||
INFURA_PROJECT_SECRET=
|
||||
INFURA_GAS_API=https://gas.api.infura.io/v3/43b945b33d58463a9246cf5ca8aa6286
|
||||
|
||||
# Ethereum Mainnet
|
||||
ETH_MAINNET_RPC_URL=https://eth-mainnet.g.alchemy.com/v2/YOUR_ALCHEMY_KEY
|
||||
# Alternative: ETH_MAINNET_RPC_URL=https://mainnet.infura.io/v3/YOUR_INFURA_KEY
|
||||
ETH_MAINNET_RPC_URL=https://mainnet.infura.io/v3/43b945b33d58463a9246cf5ca8aa6286
|
||||
ETHEREUM_MAINNET_RPC=https://mainnet.infura.io/v3/43b945b33d58463a9246cf5ca8aa6286
|
||||
|
||||
# Cronos (Crypto.com)
|
||||
CRONOS_RPC_URL=https://evm.cronos.org
|
||||
CRONOS_MAINNET_RPC=https://evm.cronos.org
|
||||
|
||||
# BSC (BNB Smart Chain)
|
||||
BSC_RPC_URL=https://bsc-dataseed1.binance.org
|
||||
# Alternative: BSC_RPC_URL=https://bsc-dataseed.binance.org
|
||||
BSC_RPC_URL=https://bsc-mainnet.infura.io/v3/43b945b33d58463a9246cf5ca8aa6286
|
||||
BSC_MAINNET_RPC=https://bsc-mainnet.infura.io/v3/43b945b33d58463a9246cf5ca8aa6286
|
||||
|
||||
# Polygon PoS
|
||||
POLYGON_RPC_URL=https://polygon-rpc.com
|
||||
# Alternative: POLYGON_RPC_URL=https://rpc-mainnet.maticvigil.com
|
||||
POLYGON_RPC_URL=https://polygon-mainnet.infura.io/v3/43b945b33d58463a9246cf5ca8aa6286
|
||||
POLYGON_MAINNET_RPC=https://polygon-mainnet.infura.io/v3/43b945b33d58463a9246cf5ca8aa6286
|
||||
|
||||
# Gnosis Chain (PoA)
|
||||
GNOSIS_RPC_URL=https://rpc.gnosischain.com
|
||||
# Alternative: GNOSIS_RPC_URL=https://xdai-archive.blockscout.com
|
||||
GNOSIS_MAINNET_RPC=https://rpc.gnosischain.com
|
||||
|
||||
# Avalanche C-Chain
|
||||
AVALANCHE_RPC_URL=https://api.avax.network/ext/bc/C/rpc
|
||||
# Alternative: AVALANCHE_RPC_URL=https://avalanche-mainnet.infura.io/v3/YOUR_KEY
|
||||
AVALANCHE_RPC_URL=https://avalanche-mainnet.infura.io/v3/43b945b33d58463a9246cf5ca8aa6286
|
||||
AVALANCHE_MAINNET_RPC=https://avalanche-mainnet.infura.io/v3/43b945b33d58463a9246cf5ca8aa6286
|
||||
|
||||
# Base
|
||||
BASE_RPC_URL=https://mainnet.base.org
|
||||
# Alternative: BASE_RPC_URL=https://base-mainnet.g.alchemy.com/v2/YOUR_KEY
|
||||
BASE_RPC_URL=https://base-mainnet.infura.io/v3/43b945b33d58463a9246cf5ca8aa6286
|
||||
BASE_MAINNET_RPC=https://base-mainnet.infura.io/v3/43b945b33d58463a9246cf5ca8aa6286
|
||||
|
||||
# Arbitrum One
|
||||
ARBITRUM_RPC_URL=https://arb1.arbitrum.io/rpc
|
||||
# Alternative: ARBITRUM_RPC_URL=https://arbitrum-mainnet.infura.io/v3/YOUR_KEY
|
||||
ARBITRUM_RPC_URL=https://arbitrum-mainnet.infura.io/v3/43b945b33d58463a9246cf5ca8aa6286
|
||||
ARBITRUM_MAINNET_RPC=https://arbitrum-mainnet.infura.io/v3/43b945b33d58463a9246cf5ca8aa6286
|
||||
|
||||
# Optimism
|
||||
OPTIMISM_RPC_URL=https://mainnet.optimism.io
|
||||
# Alternative: OPTIMISM_RPC_URL=https://optimism-mainnet.infura.io/v3/YOUR_KEY
|
||||
OPTIMISM_RPC_URL=https://optimism-mainnet.infura.io/v3/43b945b33d58463a9246cf5ca8aa6286
|
||||
OPTIMISM_MAINNET_RPC=https://optimism-mainnet.infura.io/v3/43b945b33d58463a9246cf5ca8aa6286
|
||||
|
||||
# Celo
|
||||
CELO_RPC_URL=https://celo-mainnet.infura.io/v3/43b945b33d58463a9246cf5ca8aa6286
|
||||
CELO_MAINNET_RPC=https://celo-mainnet.infura.io/v3/43b945b33d58463a9246cf5ca8aa6286
|
||||
|
||||
# Chain-138 (DeFi Oracle Meta Mainnet)
|
||||
# For deployment use Core RPC (IP:port); do not use FQDN (DNS/tunnel can fail).
|
||||
@@ -229,6 +239,11 @@ CCIPLOGGER_MAINNET=
|
||||
# =============================================================================
|
||||
# Default: CW_BRIDGE_ADDRESS=0x0; use per-chain overrides (best practice). In .env, CW_BRIDGE_<CHAIN> are set from the deployed bridge suite (CCIP_RELAY_BRIDGE_MAINNET, CCIPWETH9_BRIDGE_*).
|
||||
# Per-chain: CW_BRIDGE_MAINNET, CW_BRIDGE_CRONOS, CW_BRIDGE_BSC, CW_BRIDGE_POLYGON, CW_BRIDGE_GNOSIS, CW_BRIDGE_AVALANCHE, CW_BRIDGE_BASE, CW_BRIDGE_ARBITRUM, CW_BRIDGE_OPTIMISM
|
||||
# Bridge-only operational-role defaults for new cW* deployments:
|
||||
CW_STRICT_MODE=1
|
||||
CW_FREEZE_OPERATIONAL_ROLES=1
|
||||
# Optional: governance multisig/controller that should hold admin and governance metadata roles on newer cW* deployments
|
||||
CW_GOVERNANCE_ADMIN=
|
||||
# After deploy, set:
|
||||
# CWUSDT_CRONOS=0x...
|
||||
# CWUSDC_CRONOS=0x...
|
||||
@@ -338,4 +353,3 @@ WETH10_ADDRESS=${WETH10_MAINNET}
|
||||
```
|
||||
|
||||
3. Never commit `.env` to version control!
|
||||
|
||||
|
||||
41
docs/hybx-omnl/CCIP_MIRROR_FLOW.md
Normal file
41
docs/hybx-omnl/CCIP_MIRROR_FLOW.md
Normal 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`.
|
||||
24
docs/hybx-omnl/DEPLOYMENT_CHECKLIST.md
Normal file
24
docs/hybx-omnl/DEPLOYMENT_CHECKLIST.md
Normal 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).
|
||||
31
docs/hybx-omnl/EXTERNAL_AUDIT_CHECKLIST.md
Normal file
31
docs/hybx-omnl/EXTERNAL_AUDIT_CHECKLIST.md
Normal 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 |
|
||||
|---------|----------|-------------|-------------|
|
||||
| | | | |
|
||||
54
docs/hybx-omnl/HYBX_OMNL_POLICY_SPEC.md
Normal file
54
docs/hybx-omnl/HYBX_OMNL_POLICY_SPEC.md
Normal 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 line’s 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.
|
||||
67
docs/hybx-omnl/OMNL_IPSAS_API.md
Normal file
67
docs/hybx-omnl/OMNL_IPSAS_API.md
Normal 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.
|
||||
55
docs/hybx-omnl/OMNL_RECONCILE_CRON_AND_CI.md
Normal file
55
docs/hybx-omnl/OMNL_RECONCILE_CRON_AND_CI.md
Normal 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.
|
||||
45
docs/hybx-omnl/OPERATIONAL_COMPLIANCE.md
Normal file
45
docs/hybx-omnl/OPERATIONAL_COMPLIANCE.md
Normal 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
24
docs/hybx-omnl/README.md
Normal 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`).
|
||||
30
docs/hybx-omnl/RUNBOOK_CIRCUIT_BREAKERS.md
Normal file
30
docs/hybx-omnl/RUNBOOK_CIRCUIT_BREAKERS.md
Normal 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 1–5 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`).
|
||||
30
docs/hybx-omnl/SECURITY_THREAT_MODEL.md
Normal file
30
docs/hybx-omnl/SECURITY_THREAT_MODEL.md
Normal 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.
|
||||
9
docs/hybx-omnl/ZK_INTEGRATION.md
Normal file
9
docs/hybx-omnl/ZK_INTEGRATION.md
Normal 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.
|
||||
28
foundry.toml
28
foundry.toml
@@ -6,6 +6,13 @@
|
||||
# `bash scripts/forge/scope.sh ...` so Forge only sees the contract subtree you
|
||||
# are actively editing.
|
||||
src = "contracts"
|
||||
# Vendored Uniswap V2 trees pin solc 0.5.x / 0.6.x; skip lets `forge test` use 0.8.20
|
||||
# without legacy solc installs. Nothing outside vendor/ imports these paths.
|
||||
skip = [
|
||||
"contracts/vendor/uniswap-v2-core/**/*.sol",
|
||||
"contracts/vendor/uniswap-v2-periphery/**/*.sol",
|
||||
"contracts/vendor/sushiswap-v2/**/*.sol",
|
||||
]
|
||||
out = "out"
|
||||
libs = ["lib"]
|
||||
solc = "0.8.20"
|
||||
@@ -53,6 +60,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" }
|
||||
|
||||
@@ -27,6 +27,6 @@ describe('frontend network config', () => {
|
||||
expect(networks.chain2138TestnetEnabled).toBe(true)
|
||||
expect(networks.frontendSourceChainIds).toEqual([138, 2138])
|
||||
expect(networks.defaultFrontendChainId).toBe(2138)
|
||||
expect(networks.defaultFrontendChainName).toBe('Defi Oracle Meta Testnet')
|
||||
expect(networks.defaultFrontendChainName).toBe('DeFi Oracle Meta Testnet')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -30,10 +30,10 @@ const configuredDefaultFrontendChainId = Number(
|
||||
import.meta.env.VITE_DEFAULT_FRONTEND_CHAIN_ID || 138
|
||||
)
|
||||
|
||||
/** Chain 2138 - Defi Oracle Meta Testnet (optional; enable with VITE_ENABLE_CHAIN2138) */
|
||||
/** Chain 2138 - DeFi Oracle Meta Testnet (optional; enable with VITE_ENABLE_CHAIN2138) */
|
||||
export const chain2138Testnet = defineChain({
|
||||
id: 2138,
|
||||
name: 'Defi Oracle Meta Testnet',
|
||||
name: 'DeFi Oracle Meta Testnet',
|
||||
network: 'chain2138-testnet',
|
||||
nativeCurrency: {
|
||||
decimals: 18,
|
||||
@@ -46,7 +46,7 @@ export const chain2138Testnet = defineChain({
|
||||
},
|
||||
blockExplorers: {
|
||||
default: {
|
||||
name: 'Defi Oracle Meta Testnet Explorer',
|
||||
name: 'DeFi Oracle Meta Testnet Explorer',
|
||||
url: explorerUrl2138,
|
||||
},
|
||||
},
|
||||
|
||||
@@ -24,6 +24,11 @@
|
||||
"forge:test:vault": "FORGE_SCOPE=vault bash scripts/forge/scope.sh test --match-path 'test/vault/Ledger.t.sol'",
|
||||
"forge:test:iso": "FORGE_SCOPE=iso4217w bash scripts/forge/scope.sh test --match-path 'test/iso4217w/*.t.sol'",
|
||||
"forge:test:quick": "FORGE_SCOPE=vault bash scripts/forge/scope.sh test --match-contract LedgerTest",
|
||||
"forge:test:omnl": "bash scripts/forge/scope.sh test hybx-omnl",
|
||||
"omnl:verify": "bash scripts/hybx-omnl/verify-deployment.sh",
|
||||
"omnl:reconcile:artifact": "bash scripts/hybx-omnl/omnl-reconcile-artifact.sh",
|
||||
"omnl:validate-cross-chain": "node scripts/hybx-omnl/validate-cross-chain-config.mjs",
|
||||
"omnl:sync-publish": "bash scripts/hybx-omnl/sync-to-publish.sh",
|
||||
"dodo-pools:create": "bash scripts/create-all-dodo-pools-from-token-api.sh",
|
||||
"dodo-pools:dry-run": "DRY_RUN=true bash scripts/create-all-dodo-pools-from-token-api.sh",
|
||||
"prereqs": "bash scripts/deployment/ensure-prerequisites.sh",
|
||||
|
||||
@@ -12,9 +12,9 @@ import {CompliantWrappedToken} from "../../contracts/tokens/CompliantWrappedToke
|
||||
* Env:
|
||||
* PRIVATE_KEY (required)
|
||||
* CW_BRIDGE_ADDRESS (required) — address that can mint/burn (e.g. CCIP receiver or custom bridge)
|
||||
* CW_STRICT_MODE=1 (optional) — revoke deployer MINTER/BURNER after bridge grant
|
||||
* CW_STRICT_MODE=0 (optional override) — by default deployer MINTER/BURNER are revoked after bridge grant
|
||||
* CW_GOVERNANCE_ADMIN=0x... (optional) — grant DEFAULT_ADMIN_ROLE to governance; if strict, revoke deployer admin when governance is set
|
||||
* CW_FREEZE_OPERATIONAL_ROLES=1 (optional) — freeze future MINTER/BURNER changes after setup
|
||||
* CW_FREEZE_OPERATIONAL_ROLES=0 (optional override) — by default freeze future MINTER/BURNER changes after setup
|
||||
* DEPLOY_CWUSDT=1, DEPLOY_CWUSDC=1, DEPLOY_CWUSDW=1, DEPLOY_CWEURC=1, ... (default all 1; set 0 to skip a token)
|
||||
*/
|
||||
contract DeployCWTokens is Script {
|
||||
@@ -24,8 +24,8 @@ contract DeployCWTokens is Script {
|
||||
uint256 pk = vm.envUint("PRIVATE_KEY");
|
||||
address deployer = vm.addr(pk);
|
||||
address bridge = vm.envAddress("CW_BRIDGE_ADDRESS");
|
||||
bool strictMode = vm.envOr("CW_STRICT_MODE", uint256(0)) == 1;
|
||||
bool freezeOperationalRoles = vm.envOr("CW_FREEZE_OPERATIONAL_ROLES", uint256(0)) == 1;
|
||||
bool strictMode = vm.envOr("CW_STRICT_MODE", uint256(1)) == 1;
|
||||
bool freezeOperationalRoles = vm.envOr("CW_FREEZE_OPERATIONAL_ROLES", uint256(1)) == 1;
|
||||
address governanceAdmin = vm.envOr("CW_GOVERNANCE_ADMIN", address(0));
|
||||
require(bridge != address(0), "CW_BRIDGE_ADDRESS required");
|
||||
|
||||
|
||||
@@ -14,6 +14,9 @@ import {CompliantWrappedToken} from "../../contracts/tokens/CompliantWrappedToke
|
||||
* CW_TOKEN_NAME (required)
|
||||
* CW_TOKEN_SYMBOL (required)
|
||||
* CW_TOKEN_DECIMALS (optional, default 6)
|
||||
* CW_STRICT_MODE=0 (optional override) — by default deployer MINTER/BURNER are revoked after bridge grant
|
||||
* CW_GOVERNANCE_ADMIN=0x... (optional) — grant DEFAULT_ADMIN_ROLE to governance; if strict, revoke deployer admin when governance is set
|
||||
* CW_FREEZE_OPERATIONAL_ROLES=0 (optional override) — by default freeze future MINTER/BURNER changes after setup
|
||||
*/
|
||||
contract DeploySingleCWToken is Script {
|
||||
function run() external {
|
||||
@@ -23,6 +26,9 @@ contract DeploySingleCWToken is Script {
|
||||
string memory tokenName = vm.envString("CW_TOKEN_NAME");
|
||||
string memory tokenSymbol = vm.envString("CW_TOKEN_SYMBOL");
|
||||
uint8 decimals_ = uint8(vm.envOr("CW_TOKEN_DECIMALS", uint256(6)));
|
||||
bool strictMode = vm.envOr("CW_STRICT_MODE", uint256(1)) == 1;
|
||||
bool freezeOperationalRoles = vm.envOr("CW_FREEZE_OPERATIONAL_ROLES", uint256(1)) == 1;
|
||||
address governanceAdmin = vm.envOr("CW_GOVERNANCE_ADMIN", address(0));
|
||||
|
||||
require(bridge != address(0), "CW_BRIDGE_ADDRESS required");
|
||||
require(bytes(tokenName).length != 0, "CW_TOKEN_NAME required");
|
||||
@@ -34,10 +40,32 @@ contract DeploySingleCWToken is Script {
|
||||
token.grantRole(token.MINTER_ROLE(), bridge);
|
||||
token.grantRole(token.BURNER_ROLE(), bridge);
|
||||
|
||||
if (strictMode) {
|
||||
token.revokeRole(token.MINTER_ROLE(), admin);
|
||||
token.revokeRole(token.BURNER_ROLE(), admin);
|
||||
}
|
||||
|
||||
if (governanceAdmin != address(0) && governanceAdmin != admin) {
|
||||
token.grantRole(token.DEFAULT_ADMIN_ROLE(), governanceAdmin);
|
||||
}
|
||||
|
||||
if (freezeOperationalRoles) {
|
||||
token.freezeOperationalRoles();
|
||||
}
|
||||
|
||||
if (strictMode && governanceAdmin != address(0) && governanceAdmin != admin) {
|
||||
token.revokeRole(token.DEFAULT_ADMIN_ROLE(), admin);
|
||||
}
|
||||
|
||||
vm.stopBroadcast();
|
||||
|
||||
console.log(tokenSymbol, address(token));
|
||||
console.log(" bridge", bridge);
|
||||
console.log(" strictMode", strictMode);
|
||||
console.log(" governanceAdmin", governanceAdmin);
|
||||
console.log(" operationalRolesFrozen", token.operationalRolesFrozen());
|
||||
console.log(" deployerHasMinter", token.hasRole(token.MINTER_ROLE(), admin));
|
||||
console.log(" deployerHasBurner", token.hasRole(token.BURNER_ROLE(), admin));
|
||||
console.log(" bridgeHasMinter", token.hasRole(token.MINTER_ROLE(), bridge));
|
||||
console.log(" bridgeHasBurner", token.hasRole(token.BURNER_ROLE(), bridge));
|
||||
}
|
||||
|
||||
@@ -7,9 +7,10 @@ import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
||||
|
||||
/**
|
||||
* @title AddLiquidityPMMPoolsChain138
|
||||
* @notice Add liquidity to the three DODO PMM pools on Chain 138 (cUSDT/cUSDC, cUSDT/USDT, cUSDC/USDC).
|
||||
* @notice Add liquidity to the canonical DODO PMM pools on Chain 138, including the stable/WETH lanes.
|
||||
* @dev Env: PRIVATE_KEY, RPC_URL_138, DODO_PMM_INTEGRATION_ADDRESS (or DODO_PMM_INTEGRATION),
|
||||
* POOL_CUSDTCUSDC, POOL_CUSDTUSDT, POOL_CUSDCUSDC,
|
||||
* optional POOL_CUSDTWETH, POOL_CUSDCWETH, POOL_CEURTWETH,
|
||||
* ADD_LIQUIDITY_BASE_AMOUNT, ADD_LIQUIDITY_QUOTE_AMOUNT (e.g. 1000000e6 for 1M units, 6 decimals).
|
||||
* Optional: ADD_LIQUIDITY_CUSDTCUSDC_BASE, ADD_LIQUIDITY_CUSDTCUSDC_QUOTE, etc. for per-pool amounts.
|
||||
* Optional: NEXT_NONCE — set to pending nonce (e.g. after mints) to avoid -32001 "Nonce too low" on broadcast.
|
||||
@@ -31,6 +32,9 @@ contract AddLiquidityPMMPoolsChain138 is Script {
|
||||
address poolCusdtCusdc = vm.envOr("POOL_CUSDTCUSDC", address(0));
|
||||
address poolCusdtUsdt = vm.envOr("POOL_CUSDTUSDT", address(0));
|
||||
address poolCusdcUsdc = vm.envOr("POOL_CUSDCUSDC", address(0));
|
||||
address poolCusdtWeth = vm.envOr("POOL_CUSDTWETH", address(0));
|
||||
address poolCusdcWeth = vm.envOr("POOL_CUSDCWETH", address(0));
|
||||
address poolCeurtWeth = vm.envOr("POOL_CEURTWETH", address(0));
|
||||
|
||||
uint256 defaultBase = vm.envOr("ADD_LIQUIDITY_BASE_AMOUNT", uint256(0));
|
||||
uint256 defaultQuote = vm.envOr("ADD_LIQUIDITY_QUOTE_AMOUNT", uint256(0));
|
||||
@@ -40,6 +44,8 @@ contract AddLiquidityPMMPoolsChain138 is Script {
|
||||
address cusdc = integration.compliantUSDC();
|
||||
address usdt = integration.officialUSDT();
|
||||
address usdc = integration.officialUSDC();
|
||||
address ceurt = vm.envOr("CEURT_ADDRESS_138", address(0xdf4b71c61E5912712C1Bdd451416B9aC26949d72));
|
||||
address weth = vm.envOr("WETH9_ADDRESS_138", vm.envOr("WETH_ADDRESS_138", address(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2)));
|
||||
|
||||
// On Chain 138, cUSDT/USDT and cUSDC/USDC should point at live local mirror quote tokens.
|
||||
// If the configured quote-side addresses have no code on 138, skip those pools to avoid "call to non-contract".
|
||||
@@ -73,6 +79,30 @@ contract AddLiquidityPMMPoolsChain138 is Script {
|
||||
console.log("Added liquidity to cUSDC/USDC pool:", poolCusdcUsdc);
|
||||
}
|
||||
}
|
||||
if (poolCusdtWeth != address(0) && (defaultBase > 0 || defaultQuote > 0)) {
|
||||
uint256 b = vm.envOr("ADD_LIQUIDITY_CUSDTWETH_BASE", defaultBase);
|
||||
uint256 q = vm.envOr("ADD_LIQUIDITY_CUSDTWETH_QUOTE", defaultQuote);
|
||||
if (b > 0 && q > 0) {
|
||||
_approveAndAdd(integration, cusdt, weth, poolCusdtWeth, b, q);
|
||||
console.log("Added liquidity to cUSDT/WETH pool:", poolCusdtWeth);
|
||||
}
|
||||
}
|
||||
if (poolCusdcWeth != address(0) && (defaultBase > 0 || defaultQuote > 0)) {
|
||||
uint256 b = vm.envOr("ADD_LIQUIDITY_CUSDCWETH_BASE", defaultBase);
|
||||
uint256 q = vm.envOr("ADD_LIQUIDITY_CUSDCWETH_QUOTE", defaultQuote);
|
||||
if (b > 0 && q > 0) {
|
||||
_approveAndAdd(integration, cusdc, weth, poolCusdcWeth, b, q);
|
||||
console.log("Added liquidity to cUSDC/WETH pool:", poolCusdcWeth);
|
||||
}
|
||||
}
|
||||
if (poolCeurtWeth != address(0) && (defaultBase > 0 || defaultQuote > 0)) {
|
||||
uint256 b = vm.envOr("ADD_LIQUIDITY_CEURTWETH_BASE", defaultBase);
|
||||
uint256 q = vm.envOr("ADD_LIQUIDITY_CEURTWETH_QUOTE", defaultQuote);
|
||||
if (b > 0 && q > 0) {
|
||||
_approveAndAdd(integration, ceurt, weth, poolCeurtWeth, b, q);
|
||||
console.log("Added liquidity to cEURT/WETH pool:", poolCeurtWeth);
|
||||
}
|
||||
}
|
||||
|
||||
vm.stopBroadcast();
|
||||
}
|
||||
|
||||
@@ -22,9 +22,9 @@ interface IDODOPMMPoolQuote {
|
||||
* FLASH_QUOTE_AMOUNT_RAW gross USDC borrowed / pushed (6 decimals raw)
|
||||
*
|
||||
* Optional:
|
||||
* POOL_CWUSDC_USDC_MAINNET default 0x69776fc607e9edA8042e320e7e43f54d06c68f0E
|
||||
* CWUSDC_MAINNET default canonical cWUSDC
|
||||
* USDC_MAINNET default official USDC
|
||||
* MAX_FLASH_QUOTE_AMOUNT_RAW optional tighter local cap; must not exceed the defended-lane safe cap
|
||||
* MIN_OUT_PMM if unset, derived from querySellQuote(receiver, amount) * MIN_OUT_PMM_NUM / MIN_OUT_PMM_DEN (defaults 985/1000)
|
||||
* MIN_OUT_PMM_NUM / MIN_OUT_PMM_DEN
|
||||
* MIN_OUT_UNWIND if unset, amount + ceil(amount * AAVE_FLASH_PREMIUM_BPS / 10000) + MIN_OUT_UNWIND_BUFFER_RAW
|
||||
@@ -50,6 +50,7 @@ contract RunMainnetAaveCwusdcUsdcQuotePushOnce is Script {
|
||||
address internal constant DEFAULT_POOL = 0x69776fc607e9edA8042e320e7e43f54d06c68f0E;
|
||||
address internal constant DEFAULT_CWUSDC = 0x2de5F116bFcE3d0f922d9C8351e0c5Fc24b9284a;
|
||||
address internal constant DEFAULT_USDC = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48;
|
||||
uint256 internal constant DEFENDED_SAFE_CAP_RAW = 2_964_298;
|
||||
|
||||
function run() external {
|
||||
uint256 pk = vm.envUint("PRIVATE_KEY");
|
||||
@@ -60,6 +61,9 @@ contract RunMainnetAaveCwusdcUsdcQuotePushOnce is Script {
|
||||
address usdc = vm.envOr("USDC_MAINNET", DEFAULT_USDC);
|
||||
address unwinder = vm.envAddress("QUOTE_PUSH_EXTERNAL_UNWINDER_MAINNET");
|
||||
uint256 amount = vm.envUint("FLASH_QUOTE_AMOUNT_RAW");
|
||||
uint256 localCap = vm.envOr("MAX_FLASH_QUOTE_AMOUNT_RAW", DEFENDED_SAFE_CAP_RAW);
|
||||
|
||||
_validateDefendedLane(pool, amount, localCap);
|
||||
|
||||
uint256 minPmmNum = vm.envOr("MIN_OUT_PMM_NUM", uint256(985));
|
||||
uint256 minPmmDen = vm.envOr("MIN_OUT_PMM_DEN", uint256(1000));
|
||||
@@ -150,4 +154,10 @@ contract RunMainnetAaveCwusdcUsdcQuotePushOnce is Script {
|
||||
AaveQuotePushFlashReceiver(receiver).flashQuotePush(usdc, amount, p);
|
||||
vm.stopBroadcast();
|
||||
}
|
||||
|
||||
function _validateDefendedLane(address pool, uint256 amount, uint256 localCap) internal pure {
|
||||
require(pool == DEFAULT_POOL, "defended pool only");
|
||||
require(localCap <= DEFENDED_SAFE_CAP_RAW, "local cap exceeds defended safe cap");
|
||||
require(amount <= localCap, "flash amount exceeds cap");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ contract RunManagedMainnetAaveCwusdcUsdcQuotePushCycle is Script {
|
||||
address internal constant DEFAULT_POOL = 0x69776fc607e9edA8042e320e7e43f54d06c68f0E;
|
||||
address internal constant DEFAULT_CWUSDC = 0x2de5F116bFcE3d0f922d9C8351e0c5Fc24b9284a;
|
||||
address internal constant DEFAULT_USDC = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48;
|
||||
uint256 internal constant DEFENDED_SAFE_CAP_RAW = 2_964_298;
|
||||
|
||||
function run() external {
|
||||
uint256 pk = vm.envUint("PRIVATE_KEY");
|
||||
@@ -42,9 +43,12 @@ contract RunManagedMainnetAaveCwusdcUsdcQuotePushCycle is Script {
|
||||
address usdc = vm.envOr("USDC_MAINNET", DEFAULT_USDC);
|
||||
address unwinder = vm.envAddress("QUOTE_PUSH_EXTERNAL_UNWINDER_MAINNET");
|
||||
uint256 amount = vm.envUint("FLASH_QUOTE_AMOUNT_RAW");
|
||||
uint256 localCap = vm.envOr("MAX_FLASH_QUOTE_AMOUNT_RAW", DEFENDED_SAFE_CAP_RAW);
|
||||
bool harvest = vm.envOr("QUOTE_PUSH_TREASURY_HARVEST", uint256(1)) == 1;
|
||||
uint256 gasHoldbackTargetRaw = vm.envOr("QUOTE_PUSH_TREASURY_GAS_HOLDBACK_TARGET_RAW", uint256(0));
|
||||
|
||||
_validateDefendedLane(pool, amount, localCap);
|
||||
|
||||
QuotePushTreasuryManager manager = QuotePushTreasuryManager(managerAddr);
|
||||
AaveQuotePushFlashReceiver.QuotePushParams memory p =
|
||||
_loadQuotePushParams(receiver, pool, integration, baseToken, unwinder, amount);
|
||||
@@ -70,6 +74,12 @@ contract RunManagedMainnetAaveCwusdcUsdcQuotePushCycle is Script {
|
||||
console.log("receiverSweepableAfter", manager.receiverSweepableQuote());
|
||||
}
|
||||
|
||||
function _validateDefendedLane(address pool, uint256 amount, uint256 localCap) internal pure {
|
||||
require(pool == DEFAULT_POOL, "defended pool only");
|
||||
require(localCap <= DEFENDED_SAFE_CAP_RAW, "local cap exceeds defended safe cap");
|
||||
require(amount <= localCap, "flash amount exceeds cap");
|
||||
}
|
||||
|
||||
function _loadQuotePushParams(
|
||||
address receiver,
|
||||
address pool,
|
||||
|
||||
25
script/hybx-omnl/DeployMirrorCoordinator.s.sol
Normal file
25
script/hybx-omnl/DeployMirrorCoordinator.s.sol
Normal 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));
|
||||
}
|
||||
}
|
||||
30
script/hybx-omnl/DeployOMNLStack.s.sol
Normal file
30
script/hybx-omnl/DeployOMNLStack.s.sol
Normal 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));
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
21
script/universal-resource/DeployPolicyProfileRegistry.s.sol
Normal file
21
script/universal-resource/DeployPolicyProfileRegistry.s.sol
Normal 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);
|
||||
}
|
||||
}
|
||||
404
scripts/deployment/build-gas-pmm-execution-bundle.sh
Executable file
404
scripts/deployment/build-gas-pmm-execution-bundle.sh
Executable 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"
|
||||
284
scripts/deployment/build-optimism-cronos-dodo-4-pools-bundle.sh
Executable file
284
scripts/deployment/build-optimism-cronos-dodo-4-pools-bundle.sh
Executable 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"
|
||||
232
scripts/deployment/c138-cw-bridge-mainnet-pct.sh
Executable file
232
scripts/deployment/c138-cw-bridge-mainnet-pct.sh
Executable 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
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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 ""
|
||||
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
231
scripts/deployment/configure-cw-public-bridge-mesh.sh
Executable file
231
scripts/deployment/configure-cw-public-bridge-mesh.sh
Executable 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."
|
||||
81
scripts/deployment/create-uniswap-v3-gas-pool.sh
Executable file
81
scripts/deployment/create-uniswap-v3-gas-pool.sh
Executable 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
|
||||
299
scripts/deployment/cw-enforce-bridge-only-roles.sh
Executable file
299
scripts/deployment/cw-enforce-bridge-only-roles.sh
Executable 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
|
||||
@@ -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 "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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" \
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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 \
|
||||
|
||||
@@ -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"
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
|
||||
54
scripts/deployment/deploy-chain138-gas-canonicals.sh
Normal file
54
scripts/deployment/deploy-chain138-gas-canonicals.sh
Normal 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[@]}"
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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}"
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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!"
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
|
||||
@@ -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" \
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user