Compare commits
2 Commits
docs/explo
...
feat/token
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
87544a4835 | ||
|
|
e4c6514f49 |
210
config/mainnet-aave-dex-parity.json
Normal file
210
config/mainnet-aave-dex-parity.json
Normal file
@@ -0,0 +1,210 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"schemaVersion": 1,
|
||||
"description": "Ethereum mainnet Uniswap V3 Quoter v1 legs for Aave V3 top-supply underlyings. Implied USD = USDC out / 10^6 for amountIn of the asset. Extend `assets` as pools change.",
|
||||
"quoterV1": "0xb27308f9F90D607463bb33eA1BeBb41C27CE5AB6",
|
||||
"chainId": 1,
|
||||
"referenceStable": {
|
||||
"symbol": "USDC",
|
||||
"address": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
|
||||
"decimals": 6
|
||||
},
|
||||
"weth": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
|
||||
"feeTiersTry": [100, 500, 3000, 10000],
|
||||
"assets": [
|
||||
{
|
||||
"symbol": "weETH",
|
||||
"address": "0xCd5fE23C85820F7B72D0926FC9b05b43E359b7ee",
|
||||
"decimals": 18,
|
||||
"amountIn": "1000000000000000000",
|
||||
"quote": {
|
||||
"type": "path",
|
||||
"tokens": [
|
||||
"0xCd5fE23C85820F7B72D0926FC9b05b43E359b7ee",
|
||||
"0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
|
||||
"0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
|
||||
],
|
||||
"fees": [500, 500],
|
||||
"label": "weETH->WETH@500->USDC@500"
|
||||
}
|
||||
},
|
||||
{
|
||||
"symbol": "wstETH",
|
||||
"address": "0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0",
|
||||
"decimals": 18,
|
||||
"amountIn": "1000000000000000000",
|
||||
"quote": {
|
||||
"type": "path",
|
||||
"tokens": [
|
||||
"0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0",
|
||||
"0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
|
||||
"0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
|
||||
],
|
||||
"fees": [100, 500],
|
||||
"label": "wstETH->WETH@100->USDC@500"
|
||||
}
|
||||
},
|
||||
{
|
||||
"symbol": "WBTC",
|
||||
"address": "0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599",
|
||||
"decimals": 8,
|
||||
"amountIn": "100000000",
|
||||
"quote": {
|
||||
"type": "path",
|
||||
"tokens": [
|
||||
"0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599",
|
||||
"0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
|
||||
],
|
||||
"fees": [500],
|
||||
"label": "WBTC->USDC@500 (canonical deep pool)"
|
||||
}
|
||||
},
|
||||
{
|
||||
"symbol": "cbBTC",
|
||||
"address": "0xcbB7C0000aB88B473b1f5aFd9ef808440eed33Bf",
|
||||
"decimals": 8,
|
||||
"amountIn": "100000000",
|
||||
"quote": {
|
||||
"type": "path",
|
||||
"tokens": [
|
||||
"0xcbB7C0000aB88B473b1f5aFd9ef808440eed33Bf",
|
||||
"0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599",
|
||||
"0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
|
||||
],
|
||||
"fees": [500, 500],
|
||||
"label": "cbBTC->WBTC@500->USDC@500"
|
||||
}
|
||||
},
|
||||
{
|
||||
"symbol": "rsETH",
|
||||
"address": "0xA1290d69c65A6Fe4DF752f95823fae25cB99e5A7",
|
||||
"decimals": 18,
|
||||
"amountIn": "1000000000000000000",
|
||||
"quote": {
|
||||
"type": "compose_via_weth",
|
||||
"feeTiersToWeth": [100, 500, 3000, 10000],
|
||||
"usdcFee": 500,
|
||||
"label": "rsETH->WETH (best tier) × WETH->USDC@500"
|
||||
}
|
||||
},
|
||||
{
|
||||
"symbol": "USDT",
|
||||
"address": "0xdAC17F958D2ee523a2206206994597C13D831ec7",
|
||||
"decimals": 6,
|
||||
"amountIn": "1000000",
|
||||
"quote": {
|
||||
"type": "single_best",
|
||||
"tokenOut": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
|
||||
"label": "USDT->USDC (best of fee tiers)"
|
||||
}
|
||||
},
|
||||
{
|
||||
"symbol": "sUSDe",
|
||||
"address": "0x9D39A5DE30e57443BfF2A8307A4256c8797A3497",
|
||||
"decimals": 18,
|
||||
"amountIn": "1000000000000000000",
|
||||
"quote": {
|
||||
"type": "single_best",
|
||||
"tokenOut": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
|
||||
"label": "sUSDe->USDC (best of fee tiers)"
|
||||
}
|
||||
},
|
||||
{
|
||||
"symbol": "WETH",
|
||||
"address": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
|
||||
"decimals": 18,
|
||||
"amountIn": "1000000000000000000",
|
||||
"quote": {
|
||||
"type": "single_best",
|
||||
"tokenOut": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
|
||||
"label": "WETH->USDC (best of fee tiers)"
|
||||
}
|
||||
},
|
||||
{
|
||||
"symbol": "USDC",
|
||||
"address": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
|
||||
"decimals": 6,
|
||||
"amountIn": "1000000",
|
||||
"quote": {
|
||||
"type": "identity_usd",
|
||||
"label": "1 USDC = 1 USD reference"
|
||||
}
|
||||
},
|
||||
{
|
||||
"symbol": "USDe",
|
||||
"address": "0x4c9EDD5852cd905f086C759E8383e09bff1E68B3",
|
||||
"decimals": 18,
|
||||
"amountIn": "1000000000000000000",
|
||||
"quote": {
|
||||
"type": "single_best",
|
||||
"tokenOut": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
|
||||
"label": "USDe->USDC (best of fee tiers)"
|
||||
}
|
||||
},
|
||||
{
|
||||
"symbol": "PT-sUSDE-7MAY2026",
|
||||
"address": "0x3de0ff76E8b528C092d47b9DaC775931cef80F49",
|
||||
"decimals": 18,
|
||||
"amountIn": "1000000000000000000",
|
||||
"quote": {
|
||||
"type": "single_best",
|
||||
"tokenOut": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
|
||||
"label": "PT->USDC (best effort; verify Pendle NAV)"
|
||||
}
|
||||
},
|
||||
{
|
||||
"symbol": "osETH",
|
||||
"address": "0xf1C9acDc66974dFB6dEcB12aA385b9cD01190E38",
|
||||
"decimals": 18,
|
||||
"amountIn": "1000000000000000000",
|
||||
"quote": {
|
||||
"type": "path",
|
||||
"tokens": [
|
||||
"0xf1C9acDc66974dFB6dEcB12aA385b9cD01190E38",
|
||||
"0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
|
||||
"0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
|
||||
],
|
||||
"fees": [500, 500],
|
||||
"label": "osETH->WETH@500->USDC@500"
|
||||
}
|
||||
},
|
||||
{
|
||||
"symbol": "GHO",
|
||||
"address": "0x40D16FC0246aD3160Ccc09B8D0D3A2cD28aE6C2f",
|
||||
"decimals": 18,
|
||||
"amountIn": "1000000000000000000",
|
||||
"quote": {
|
||||
"type": "single_best",
|
||||
"tokenOut": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
|
||||
"label": "GHO->USDC (best of fee tiers)"
|
||||
}
|
||||
},
|
||||
{
|
||||
"symbol": "LBTC",
|
||||
"address": "0x8236a87084f8B84306f72007F36F2618A5634494",
|
||||
"decimals": 8,
|
||||
"amountIn": "100000000",
|
||||
"quote": {
|
||||
"type": "path",
|
||||
"tokens": [
|
||||
"0x8236a87084f8B84306f72007F36F2618A5634494",
|
||||
"0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599",
|
||||
"0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
|
||||
],
|
||||
"fees": [500, 500],
|
||||
"label": "LBTC->WBTC@500->USDC@500"
|
||||
}
|
||||
},
|
||||
{
|
||||
"symbol": "RLUSD",
|
||||
"address": "0x8292Bb45bf1Ee4d140127049757C2E0fF06317eD",
|
||||
"decimals": 18,
|
||||
"amountIn": "1000000000000000000",
|
||||
"quote": {
|
||||
"type": "single_best",
|
||||
"tokenOut": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
|
||||
"label": "RLUSD->USDC (best of fee tiers)"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -59,32 +59,6 @@ pct exec 5000 -- bash -c 'cd /opt/blockscout && docker-compose up -d blockscout'
|
||||
|
||||
---
|
||||
|
||||
## Fix: Smart-Contract Verifier Sidecar Missing
|
||||
|
||||
**Symptom:** verification endpoints exist and submissions appear accepted, but deployed contracts remain `bytecode-only` and never promote into full source metadata.
|
||||
|
||||
**Root cause:** CT `5000` is running only `blockscout` and `postgres`, without the upstream `smart-contract-verifier` sidecar and without the required verifier wiring env:
|
||||
|
||||
- `MICROSERVICE_SC_VERIFIER_ENABLED=true`
|
||||
- `MICROSERVICE_SC_VERIFIER_TYPE=sc_verifier`
|
||||
- `MICROSERVICE_SC_VERIFIER_URL=http://smart-contract-verifier:8050/`
|
||||
|
||||
**Recommended fix:**
|
||||
|
||||
```bash
|
||||
bash scripts/deployment/ensure-blockscout-smart-contract-verifier-5000.sh --dry-run
|
||||
bash scripts/deployment/ensure-blockscout-smart-contract-verifier-5000.sh --apply
|
||||
```
|
||||
|
||||
The script:
|
||||
1. Backs up `/opt/blockscout/docker-compose.yml`
|
||||
2. Adds the upstream `smart-contract-verifier` sidecar
|
||||
3. Wires Blockscout to the sidecar with `MICROSERVICE_SC_VERIFIER_*`
|
||||
4. Restarts the stack cleanly
|
||||
5. Verifies `/api/v2/smart-contracts/verification/config`
|
||||
|
||||
---
|
||||
|
||||
## Fix: Migrate VM 5000 to thin5 (has free space)
|
||||
|
||||
**Run on Proxmox host r630-02 (192.168.11.12):**
|
||||
@@ -135,8 +109,6 @@ source smom-dbis-138/.env 2>/dev/null
|
||||
./scripts/verify/run-contract-verification-with-proxy.sh
|
||||
```
|
||||
|
||||
**Important:** native `Uniswap v2` / `SushiSwap` verification should pin the exact historical compiler version. The repo submitter now does that explicitly so Forge defaults do not silently downgrade the verification attempt.
|
||||
|
||||
---
|
||||
|
||||
## Forge Verification Compatibility
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Contract Verification And Publication Matrix (All Networks)
|
||||
|
||||
**Generated:** 2026-04-16T19:20:09.634Z
|
||||
**Generated:** 2026-04-11T21:10:30.092Z
|
||||
**Authoritative sources:** `config/smart-contracts-master.json`, `cross-chain-pmm-lps/config/deployment-status.json`
|
||||
|
||||
This matrix is the canonical repo-level inventory for **what still needs explorer verification and publication coverage across every network currently tracked in the workspace**.
|
||||
@@ -15,18 +15,18 @@ This matrix is the canonical repo-level inventory for **what still needs explore
|
||||
|
||||
| Chain ID | Chain | Total Entries | Canonical Contracts | cW / Gas Mirrors | PMM Pools | Explorer |
|
||||
| --- | --- | ---: | ---: | ---: | ---: | --- |
|
||||
| 1 | Ethereum Mainnet | 40 | 3 | 14 | 17 | https://etherscan.io |
|
||||
| 10 | Optimism | 31 | 0 | 14 | 12 | https://optimistic.etherscan.io |
|
||||
| 25 | Cronos | 29 | 0 | 14 | 12 | https://cronoscan.com |
|
||||
| 56 | BSC | 28 | 0 | 16 | 10 | https://bscscan.com |
|
||||
| 100 | Gnosis | 28 | 0 | 14 | 10 | https://gnosisscan.io |
|
||||
| 137 | Polygon | 32 | 0 | 15 | 12 | https://polygonscan.com |
|
||||
| 1 | Ethereum Mainnet | 36 | 3 | 14 | 13 | https://etherscan.io |
|
||||
| 10 | Optimism | 21 | 0 | 14 | 2 | https://optimistic.etherscan.io |
|
||||
| 25 | Cronos | 19 | 0 | 14 | 2 | https://cronoscan.com |
|
||||
| 56 | BSC | 18 | 0 | 16 | 0 | https://bscscan.com |
|
||||
| 100 | Gnosis | 18 | 0 | 14 | 0 | https://gnosisscan.io |
|
||||
| 137 | Polygon | 22 | 0 | 15 | 2 | https://polygonscan.com |
|
||||
| 138 | Chain 138 | 115 | 115 | 0 | 0 | https://blockscout.defi-oracle.io |
|
||||
| 1111 | Wemix | 4 | 0 | 2 | 0 | https://explorer.wemix.com |
|
||||
| 8453 | Base | 29 | 0 | 14 | 10 | https://basescan.org |
|
||||
| 42161 | Arbitrum | 29 | 0 | 14 | 10 | https://arbiscan.io |
|
||||
| 42220 | Celo | 29 | 0 | 16 | 10 | https://celoscan.io |
|
||||
| 43114 | Avalanche | 29 | 0 | 16 | 10 | https://snowtrace.io |
|
||||
| 8453 | Base | 19 | 0 | 14 | 0 | https://basescan.org |
|
||||
| 42161 | Arbitrum | 19 | 0 | 14 | 0 | https://arbiscan.io |
|
||||
| 42220 | Celo | 19 | 0 | 16 | 0 | https://celoscan.io |
|
||||
| 43114 | Avalanche | 19 | 0 | 16 | 0 | https://snowtrace.io |
|
||||
|
||||
## Required operator path
|
||||
|
||||
@@ -71,17 +71,13 @@ The JSON report in `reports/status/contract_verification_publish_matrix.json` co
|
||||
| 1 | Ethereum Mainnet | pmm_pool | cWCADC/USDC | `0xE0F35b5736FDd0a2F4B618621b0A08F8D8A3f92A` | etherscan | inventory-only | https://etherscan.io | pending | pending |
|
||||
| 1 | Ethereum Mainnet | pmm_pool | cWCHFC/USDC | `0x776Ca556deD3245984F504F4bef8Eeec55C50190` | etherscan | inventory-only | https://etherscan.io | pending | pending |
|
||||
| 1 | Ethereum Mainnet | pmm_pool | cWEURC/USDC | `0x0bC750F9c6DbDcd76B205695A356491b1B9ef098` | etherscan | inventory-only | https://etherscan.io | pending | pending |
|
||||
| 1 | Ethereum Mainnet | pmm_pool | cWEURT/USDC | `0x9cF3DeDAaC0984c530801b9b4881c8f99Bb329c3` | etherscan | inventory-only | https://etherscan.io | pending | pending |
|
||||
| 1 | Ethereum Mainnet | pmm_pool | cWGBPC/USDC | `0x5488042dF882893a3e7074453E2005CaDE4101b0` | etherscan | inventory-only | https://etherscan.io | pending | pending |
|
||||
| 1 | Ethereum Mainnet | pmm_pool | cWGBPT/USDC | `0xA42566bb730AD6D551Db32d50c0877132fc07c32` | etherscan | inventory-only | https://etherscan.io | pending | pending |
|
||||
| 1 | Ethereum Mainnet | pmm_pool | cWJPYC/USDC | `0x8A4187dF0A8FE855cC53A4F7B2D8346588Ee9794` | etherscan | inventory-only | https://etherscan.io | pending | pending |
|
||||
| 1 | Ethereum Mainnet | pmm_pool | cWUSDC/USDC | `0x69776fc607e9edA8042e320e7e43f54d06c68f0E` | etherscan | inventory-only | https://etherscan.io | pending | pending |
|
||||
| 1 | Ethereum Mainnet | pmm_pool | cWUSDC/USDT | `0xCC0fd27A40775c9AfcD2BBd3f7c902b0192c247A` | etherscan | inventory-only | https://etherscan.io | pending | pending |
|
||||
| 1 | Ethereum Mainnet | pmm_pool | cWUSDT/cWUSDC | `0xe944b7Cb012A0820c07f54D51e92f0e1C74168DB` | etherscan | inventory-only | https://etherscan.io | pending | pending |
|
||||
| 1 | Ethereum Mainnet | pmm_pool | cWUSDT/USDC | `0x27f3aE7EE71Be3d77bAf17d4435cF8B895DD25D2` | etherscan | inventory-only | https://etherscan.io | pending | pending |
|
||||
| 1 | Ethereum Mainnet | pmm_pool | cWUSDT/USDT | `0x79156F6B7bf71a1B72D78189B540A89A6C13F6FC` | etherscan | inventory-only | https://etherscan.io | pending | pending |
|
||||
| 1 | Ethereum Mainnet | pmm_pool | cWXAUC/USDC | `0xf6470219ce7749f8860dEABe9c347Ef2c1075E08` | etherscan | inventory-only | https://etherscan.io | pending | pending |
|
||||
| 1 | Ethereum Mainnet | pmm_pool | cWXAUT/USDC | `0x1D51a38C924382287d770AbB61deb9C39ACa96E9` | etherscan | inventory-only | https://etherscan.io | pending | pending |
|
||||
| 1 | Ethereum Mainnet | pmm_pool_volatile | cWUSDC/TRUU | `0x9A632F35078b6A4A9bf27806Bb7aFfAA2F16C846` | etherscan | inventory-only | https://etherscan.io | pending | pending |
|
||||
| 1 | Ethereum Mainnet | pmm_pool_volatile | cWUSDT/TRUU | `0x508E5e80B66204b8CD9869323Fdd3A289ea50993` | etherscan | inventory-only | https://etherscan.io | pending | pending |
|
||||
| 1 | Ethereum Mainnet | reference_venue | balancer:cWETH/USDC | `0xba11000000000000000000000000000000000001` | etherscan | inventory-only | https://etherscan.io | pending | pending |
|
||||
@@ -103,18 +99,8 @@ The JSON report in `reports/status/contract_verification_publish_matrix.json` co
|
||||
| 10 | Optimism | cw_token | cWXAUC | `0xddc4063f770f7c49d00b5a10fb552e922aa39b2c` | etherscan-family | inventory-only | https://optimistic.etherscan.io | pending | pending |
|
||||
| 10 | Optimism | cw_token | cWXAUT | `0x145e8e8c49b6a021969dd9d2c01c8fea44374f61` | etherscan-family | inventory-only | https://optimistic.etherscan.io | pending | pending |
|
||||
| 10 | Optimism | gas_mirror | cWETHL2 | `0x95007ec50d0766162f77848edf7bdc4eba147fb4` | etherscan-family | inventory-only | https://optimistic.etherscan.io | pending | pending |
|
||||
| 10 | Optimism | pmm_pool | cWAUDC/USDC | `0x4B452800f6cD50326F14a6f089f4bB04e8079250` | etherscan-family | inventory-only | https://optimistic.etherscan.io | pending | pending |
|
||||
| 10 | Optimism | pmm_pool | cWCADC/USDC | `0x19e1fdd037F1651AcEE11c5A5Aa246b85FA63f8e` | etherscan-family | inventory-only | https://optimistic.etherscan.io | pending | pending |
|
||||
| 10 | Optimism | pmm_pool | cWCHFC/USDC | `0xA97D7dfB93CBf0C10243931d93FFEda745222ec6` | etherscan-family | inventory-only | https://optimistic.etherscan.io | pending | pending |
|
||||
| 10 | Optimism | pmm_pool | cWEURC/USDC | `0x78C6aC6D7CbFcd85A3291D656F2154979a92c00B` | etherscan-family | inventory-only | https://optimistic.etherscan.io | pending | pending |
|
||||
| 10 | Optimism | pmm_pool | cWEURT/USDC | `0x631DfC86A03cB05319d7165198f8099dacF78e56` | etherscan-family | inventory-only | https://optimistic.etherscan.io | pending | pending |
|
||||
| 10 | Optimism | pmm_pool | cWGBPC/USDC | `0x79BE2b70A94E954d095f9F537FAf0741D15dfA31` | etherscan-family | inventory-only | https://optimistic.etherscan.io | pending | pending |
|
||||
| 10 | Optimism | pmm_pool | cWGBPT/USDC | `0x5D6b5d7CA165c39c350083255774DdBf1c858e12` | etherscan-family | inventory-only | https://optimistic.etherscan.io | pending | pending |
|
||||
| 10 | Optimism | pmm_pool | cWJPYC/USDC | `0x68C1c8a945ddCF3482b73aC09b6B5D4177D564AF` | etherscan-family | inventory-only | https://optimistic.etherscan.io | pending | pending |
|
||||
| 10 | Optimism | pmm_pool | cWUSDC/USDC | `0x8F1038dE06d799a30D16d8B0b0ADEe629e7d4547` | etherscan-family | inventory-only | https://optimistic.etherscan.io | pending | pending |
|
||||
| 10 | Optimism | pmm_pool | cWUSDT/USDT | `0xFCB0b0Ac36d67EDBA91100c75C27De945357CD62` | etherscan-family | inventory-only | https://optimistic.etherscan.io | pending | pending |
|
||||
| 10 | Optimism | pmm_pool | cWXAUC/USDC | `0xCE25c324e41049D75abfB81c23257984A2A97a79` | etherscan-family | inventory-only | https://optimistic.etherscan.io | pending | pending |
|
||||
| 10 | Optimism | pmm_pool | cWXAUT/USDC | `0xb3Ee650019d7F756ce0F79b69614Fa2761871775` | etherscan-family | inventory-only | https://optimistic.etherscan.io | pending | pending |
|
||||
| 10 | Optimism | reference_venue | balancer:cWETHL2/USDC | `0xba2100000000000000000000000000000000000a` | etherscan-family | inventory-only | https://optimistic.etherscan.io | pending | pending |
|
||||
| 10 | Optimism | reference_venue | curve:cWETHL2/USDC | `0xc72100000000000000000000000000000000000a` | etherscan-family | inventory-only | https://optimistic.etherscan.io | pending | pending |
|
||||
| 10 | Optimism | reference_venue | uniswap_v3:cWETHL2/WETH | `0x712100000000000000000000000000000000000a` | etherscan-family | inventory-only | https://optimistic.etherscan.io | pending | pending |
|
||||
@@ -127,6 +113,20 @@ The JSON report in `reports/status/contract_verification_publish_matrix.json` co
|
||||
| 25 | Cronos | cw_token | cWEURC | `0x7574d37F42528B47c88962931e48FC61608a4050` | etherscan-family | inventory-only | https://cronoscan.com | pending | pending |
|
||||
| 25 | Cronos | cw_token | cWEURT | `0x9f833b4f1012F52eb3317b09922a79c6EdFca77D` | etherscan-family | inventory-only | https://cronoscan.com | pending | pending |
|
||||
| 25 | Cronos | cw_token | cWGBPC | `0xe5c65A76A541368d3061fe9E7A2140cABB903dbF` | etherscan-family | inventory-only | https://cronoscan.com | pending | pending |
|
||||
| 25 | Cronos | cw_token | cWGBPT | `0xBb58fa16bAc8E789f09C14243adEE6480D8213A2` | etherscan-family | inventory-only | https://cronoscan.com | pending | pending |
|
||||
| 25 | Cronos | cw_token | cWJPYC | `0x52aD62B8bD01154e2A4E067F8Dc4144C9988d203` | etherscan-family | inventory-only | https://cronoscan.com | pending | pending |
|
||||
| 25 | Cronos | cw_token | cWUSDC | `0x932566E5bB6BEBF6B035B94f3DE1f75f126304Ec` | etherscan-family | inventory-only | https://cronoscan.com | pending | pending |
|
||||
| 25 | Cronos | cw_token | cWUSDT | `0x72948a7a813B60b37Cd0c920C4657DbFF54312b8` | etherscan-family | inventory-only | https://cronoscan.com | pending | pending |
|
||||
| 25 | Cronos | cw_token | cWXAUC | `0xf1B771c95573113E993374c0c7cB2dc1a7908B12` | etherscan-family | inventory-only | https://cronoscan.com | pending | pending |
|
||||
| 25 | Cronos | cw_token | cWXAUT | `0xD517C0cF7013f988946A468c880Cc9F8e2A4BCbE` | etherscan-family | inventory-only | https://cronoscan.com | pending | pending |
|
||||
| 25 | Cronos | gas_mirror | cWCRO | `0x9b10eb0f77c45322dbd1fcb07176fd9a7609c164` | etherscan-family | inventory-only | https://cronoscan.com | pending | pending |
|
||||
| 25 | Cronos | pmm_pool | cWUSDC/USDC | `0x8F1038dE06d799a30D16d8B0b0ADEe629e7d4547` | etherscan-family | inventory-only | https://cronoscan.com | pending | pending |
|
||||
| 25 | Cronos | pmm_pool | cWUSDT/USDT | `0xFCB0b0Ac36d67EDBA91100c75C27De945357CD62` | etherscan-family | inventory-only | https://cronoscan.com | pending | pending |
|
||||
| 25 | Cronos | reference_venue | uniswap_v3:cWCRO/WCRO | `0x7161000000000000000000000000000000000019` | etherscan-family | inventory-only | https://cronoscan.com | pending | pending |
|
||||
| 56 | BSC | anchor_token | USDT | `0x55d398326f99059fF775485246999027B3197955` | etherscan-family | reference-only | https://bscscan.com | pending | pending |
|
||||
| 56 | BSC | cw_token | cWAUDC | `0x7062f35567BBAb4d98dc33af03B0d14Df42294D5` | etherscan-family | inventory-only | https://bscscan.com | pending | pending |
|
||||
| 56 | BSC | cw_token | cWAUSDT | `0xe1a51Bc037a79AB36767561B147eb41780124934` | etherscan-family | inventory-only | https://bscscan.com | pending | pending |
|
||||
| 56 | BSC | cw_token | cWBTC | `0xcb7c000000000000000000000000000000000038` | etherscan-family | inventory-only | https://bscscan.com | pending | pending |
|
||||
|
||||
## Notes
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Explorer Token List Cross-Check
|
||||
|
||||
**Last Updated:** 2026-04-14
|
||||
**Last Updated:** 2026-04-02
|
||||
**Purpose:** Cross-check the token list shown at [https://explorer.d-bis.org/tokens](https://explorer.d-bis.org/tokens) against repo token lists, canonical addresses, and CONTRACT_ADDRESSES_REFERENCE.
|
||||
|
||||
---
|
||||
@@ -89,7 +89,6 @@ and use `dbis-138.tokenlist.json` as the curated source. See §9.
|
||||
| LINK (0xb7721d…) | Yes | Match |
|
||||
| cUSDT (0x93E6…) | Yes | Match |
|
||||
| cUSDC (0xf22258…) | Yes | Match |
|
||||
| cUSDT V2, cUSDC V2, mirror USDT/USDC | Yes | Match §5; list uses tag **`fwdcanon`** (Uniswap schema tag length); gas rows use **`gasnative`** |
|
||||
| cEURC (0x808596…) | Yes | Match |
|
||||
|
||||
**Historical gap (closed 2026-02-28):**
|
||||
@@ -97,7 +96,7 @@ cEURT, cGBPC, cGBPT, cAUDC, cJPYC, cCHFC, cCADC, cXAUC, cXAUT were initially mis
|
||||
|
||||
### 3.2 `explorer-monorepo/backend/config/metamask/DUAL_CHAIN_TOKEN_LIST.tokenlist.json`
|
||||
|
||||
Chain 138 entries were originally the same 7 as `dbis-138`, but the additional compliant tokens were added on 2026-02-28. **cUSDT/cUSDC V2** and **official mirror USDT/USDC** (D3) are now part of the canonical set in **§5** and appear in `DUAL_CHAIN_TOKEN_LIST`; keep `dbis-138.tokenlist.json` aligned with §5 for packaging parity. Keep this file focused on live Explorer cross-checks rather than the closed token-list gap.
|
||||
Chain 138 entries were originally the same 7 as `dbis-138`, but the additional compliant tokens were added on 2026-02-28. Keep this file focused on live Explorer cross-checks rather than the closed token-list gap.
|
||||
|
||||
### 3.3 `ADDRESS_MATRIX_AND_STATUS.md` / `CONTRACT_ADDRESSES_REFERENCE.md`
|
||||
|
||||
@@ -111,9 +110,9 @@ All **canonical** Chain 138 token addresses (WETH, WETH10, LINK, cUSDT, cUSDC, c
|
||||
|------|--------|----------------|
|
||||
| **Explorer token list source** | Confirmed: Blockscout `GET /api/v2/tokens` | No change; document only. |
|
||||
| **WETH9 on Blockscout** | First token has `decimals: "0"`, `name`/`symbol`: null | Verify WETH9 contract metadata on-chain; fix in contract or in Blockscout indexing if needed. |
|
||||
| **dbis-138.tokenlist.json** | Done (2026-02-28; mirrors 2026-04-14) | Added cEURT, cGBPC, cGBPT, cAUDC, cJPYC, cCHFC, cCADC, cXAUC, cXAUT; **official mirror USDT/USDC** per §5. |
|
||||
| **DUAL_CHAIN_TOKEN_LIST (MetaMask)** | Done (2026-02-28) | Added same 9 tokens to backend and api/rest copies; **V2 + mirrors** tracked in §5 / DUAL. |
|
||||
| **ADDRESS_MATRIX / docs** | Done | cEURT and **§1.1 / §5** token inventory; TransactionMirror 0x7131…; summary updated. |
|
||||
| **dbis-138.tokenlist.json** | Done (2026-02-28) | Added cEURT, cGBPC, cGBPT, cAUDC, cJPYC, cCHFC, cCADC, cXAUC, cXAUT. |
|
||||
| **DUAL_CHAIN_TOKEN_LIST (MetaMask)** | Done (2026-02-28) | Added same 9 tokens to backend and api/rest copies. |
|
||||
| **ADDRESS_MATRIX / docs** | Done | cEURT and all 16 tokens in §1.1; TransactionMirror 0x7131…; summary updated. |
|
||||
| **Extra LINK/cUSDT/cUSDC on Blockscout** | 6 additional contracts | Non-canonical; use §5 canonical addresses only. |
|
||||
|
||||
---
|
||||
@@ -129,10 +128,6 @@ Use this table to align token lists and docs with the Explorer (Blockscout) and
|
||||
| LINK | `0xb7721dD53A8c629d9f1Ba31a5819AFe250002b03` | 18 |
|
||||
| cUSDT | `0x93E66202A11B1772E55407B32B44e5Cd8eda7f22` | 6 |
|
||||
| cUSDC | `0xf22258f57794CC8E06237084b353Ab30fFfa640b` | 6 |
|
||||
| cUSDT V2 | `0x9FBfab33882Efe0038DAa608185718b772EE5660` | 6 |
|
||||
| cUSDC V2 | `0x219522c60e83dEe01FC5b0329d6fA8fD84b9D13d` | 6 |
|
||||
| USDT (official mirror, D3) | `0x004b63A7B5b0E06f6bB6adb4a5F9f590BF3182D1` | 6 |
|
||||
| USDC (official mirror, D3) | `0x71D6687F38b93CCad569Fa6352c876eea967201b` | 6 |
|
||||
| cEURC | `0x8085961F9cF02b4d800A3c6d386D31da4B34266a` | 6 |
|
||||
| cEURT | `0xdf4b71c61E5912712C1Bdd451416B9aC26949d72` | 6 |
|
||||
| cGBPC | `0x003960f16D9d34F2e98d62723B6721Fb92074aD2` | 6 |
|
||||
@@ -159,7 +154,7 @@ Use this table to align token lists and docs with the Explorer (Blockscout) and
|
||||
| `explorer-monorepo/backend/config/metamask/DUAL_CHAIN_TOKEN_LIST.tokenlist.json` | Multi-chain token list (138, 1, 651940, 25) for MetaMask |
|
||||
| `token-lists/lists/dbis-138.tokenlist.json` | Chain 138 curated token list (Uniswap-style) |
|
||||
| `smom-dbis-138/services/token-aggregation/src/config/canonical-tokens.ts` | Canonical addresses and env overrides for indexing/reporting |
|
||||
| `docs/11-references/ADDRESS_MATRIX_AND_STATUS.md` | Correlated address matrix; §1.1 aligned with **§5** canonical rows; TransactionMirror and summary updated |
|
||||
| `docs/11-references/ADDRESS_MATRIX_AND_STATUS.md` | Correlated address matrix; §1.1 includes all 16 tokens; TransactionMirror and summary updated |
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -76,7 +76,7 @@
|
||||
| **DODO Vending Machine / Adapter** | Deployed | `0xb6D9EF3575bc48De3f011C310DC24d87bEC6087C` — adapter used by `DODOPMMIntegration`. |
|
||||
| **PMM pools (current canonical stack)** | 3 created | Public pools are `0x9e89bAe009adf128782E19e8341996c596ac40dC` (cUSDT/cUSDC), `0x866Cb44b59303d8dc5f4F9E3E7A8e8b0bf238d66` (cUSDT/USDT), `0xc39B7D0F40838cbFb54649d327f49a6DAC964062` (cUSDC/USDC). |
|
||||
| **DODOPMMProvider** | **Deployed** | `0x3f729632E9553EBacCdE2e9b4c8F2B285b014F2e`; supports the three canonical stable pools above. |
|
||||
| **D3Oracle** | Deployed (private pilot) | `0xD7459aEa8bB53C83a1e90262777D730539A326F0`; `WETH10` uses keeper-synced **MockPriceFeed** `0x3e8725b8De386feF3eFE5678c92eA6aDB41992B2` (avoids managed-aggregator staleness on Besu). Legacy slot `0x99b3511a2d315a497c8112c1fdd8d508d4b1e506` remains for audits. Stables use managed USD feeds. |
|
||||
| **D3Oracle** | Deployed (private pilot) | `0xD7459aEa8bB53C83a1e90262777D730539A326F0`; `WETH10` now uses live `Oracle_Aggregator=0x99b3511a2d315a497c8112c1fdd8d508d4b1e506`, and the stable assets use dedicated managed USD feeds. |
|
||||
| **D3Vault / D3Proxy / D3MMFactory** | Deployed (private pilot) | `D3Vault=0x42b6867260Fb9eE6d09B7E0233A1fAD65D0133D1`, `D3Proxy=0xc9a11abB7C63d88546Be24D58a6d95e3762cB843`, `D3MMFactory=0x78470C7d2925B6738544E2DD4FE7c07CcA21AC31`. |
|
||||
| **D3MM pools** | 2 created | `0xE71Bc2cCb62dA5B18F88647db2b4a721Db416fc5` is a superseded bootstrap pool on the placeholder `WETH9` path. `0x6550A3a59070061a262a893A1D6F3F490afFDBDA` is the canonical private `WETH10` pilot pool. |
|
||||
| **EnhancedSwapRouterV2 + public venue layer** | Live | Router-v2 is deployed and the canonical upstream-native `Uniswap_v3` lane plus the funded pilot-compatible `Balancer`, `Curve_3`, and `1inch` venues are publicly exposed for the canonical Chain 138 routing asset lanes. |
|
||||
|
||||
@@ -17,25 +17,6 @@ import http from 'node:http';
|
||||
const PORT = parseInt(process.env.PORT || '3080', 10);
|
||||
const BLOCKSCOUT_URL = (process.env.BLOCKSCOUT_URL || 'http://192.168.11.140:4000').replace(/\/$/, '');
|
||||
|
||||
function parseOptimizationRuns(payload) {
|
||||
const raw = payload.runs ?? payload.optimization_runs ?? '200';
|
||||
return parseInt(raw, 10) || 200;
|
||||
}
|
||||
|
||||
function inferOptimizationEnabled(payload) {
|
||||
const explicit = payload.optimizationUsed ?? payload.optimization_used;
|
||||
if (explicit !== undefined && explicit !== null && explicit !== '') {
|
||||
return [true, '1', 1, 'true'].includes(explicit);
|
||||
}
|
||||
// Forge often supplies runs without optimizationUsed for legacy compiler paths.
|
||||
// When runs is positive, assume optimization was intentionally enabled.
|
||||
return parseOptimizationRuns(payload) > 0;
|
||||
}
|
||||
|
||||
function inferEvmVersion(payload) {
|
||||
return payload.evmversion || payload.evm_version || 'default';
|
||||
}
|
||||
|
||||
/** Parse body as JSON or application/x-www-form-urlencoded (Forge/Etherscan style). */
|
||||
function parseBody(req) {
|
||||
return new Promise((resolve, reject) => {
|
||||
@@ -142,9 +123,9 @@ async function forwardV2Flattened(payload) {
|
||||
compiler_version: payload.compilerversion || payload.compilerVersion || 'v0.8.20+commit.a1b79de6',
|
||||
contract_name: payload.contractname || payload.contractName || 'Contract',
|
||||
license_type: payload.licensetype || payload.licenseType || 'mit',
|
||||
is_optimization_enabled: inferOptimizationEnabled(payload),
|
||||
optimization_runs: parseOptimizationRuns(payload),
|
||||
evm_version: inferEvmVersion(payload),
|
||||
is_optimization_enabled: [true, '1', 1, 'true'].includes(payload.optimizationUsed ?? payload.optimization_used),
|
||||
optimization_runs: parseInt(payload.runs ?? payload.optimization_runs ?? '200', 10) || 200,
|
||||
evm_version: payload.evmversion || payload.evm_version || 'london',
|
||||
autodetect_constructor_args: payload.autodetectConstructorArguments !== false,
|
||||
source_code: typeof sourceCode === 'string' ? sourceCode : JSON.stringify(sourceCode),
|
||||
};
|
||||
@@ -225,9 +206,13 @@ async function forwardV2StandardInput(payload) {
|
||||
appendField('autodetect_constructor_args', String(payload.autodetectConstructorArguments !== false));
|
||||
appendField('license_type', licenseType);
|
||||
appendField('constructor_args', constructorArgs);
|
||||
appendField('evm_version', inferEvmVersion(payload));
|
||||
appendField('is_optimization_enabled', String(inferOptimizationEnabled(payload)));
|
||||
appendField('optimization_runs', String(parseOptimizationRuns(payload)));
|
||||
if (payload.evmversion || payload.evm_version) appendField('evm_version', payload.evmversion || payload.evm_version);
|
||||
if (payload.optimizationUsed !== undefined || payload.optimization_used !== undefined) {
|
||||
appendField('is_optimization_enabled', String([true, '1', 1, 'true'].includes(payload.optimizationUsed ?? payload.optimization_used)));
|
||||
}
|
||||
if (payload.runs !== undefined || payload.optimization_runs !== undefined) {
|
||||
appendField('optimization_runs', String(parseInt(payload.runs ?? payload.optimization_runs ?? '200', 10) || 200));
|
||||
}
|
||||
appendFile('files[0]', 'standard-input.json', standardJson, 'application/json');
|
||||
parts.push(Buffer.from(`--${boundary}--\r\n`));
|
||||
const body = Buffer.concat(parts);
|
||||
@@ -269,15 +254,7 @@ function toEtherscanResponse(result) {
|
||||
return { status: '1', message: data.message || 'OK', result: data.result ?? 'Verification submitted' };
|
||||
}
|
||||
if (status >= 200 && status < 300) {
|
||||
const successMessage = typeof data?.message === 'string' ? data.message : '';
|
||||
const successResult = typeof data?.result === 'string' ? data.result : '';
|
||||
if (
|
||||
/verification started/i.test(successMessage) ||
|
||||
/verification submitted/i.test(successMessage) ||
|
||||
/verification submitted/i.test(successResult)
|
||||
) {
|
||||
return { status: '1', message: successMessage || 'OK', result: successResult || 'Verification submitted' };
|
||||
}
|
||||
return { status: '1', message: 'OK', result: data?.result ?? 'Verification submitted' };
|
||||
}
|
||||
// Blockscout may return HTML (502/500) or invalid JSON when DB/migrations fail
|
||||
let msg = data?.message || data?.error;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -115,6 +115,8 @@ try {
|
||||
} catch (_) {
|
||||
process.exit(0);
|
||||
}
|
||||
NODE
|
||||
}
|
||||
|
||||
derive_repo_example_env_value() {
|
||||
local env_key="$1"
|
||||
@@ -214,6 +216,7 @@ NODE
|
||||
sync_chain138_public_rpc_env() {
|
||||
local env_file="$1"
|
||||
local public_chain138_rpc="${TOKEN_AGG_CHAIN138_RPC_URL:-http://192.168.11.221:8545}"
|
||||
local token_agg_port="${TOKEN_AGG_PORT:-3001}"
|
||||
|
||||
# Explorer-side read services must use the public Chain 138 RPC node, not the
|
||||
# operator/deploy core RPC.
|
||||
@@ -230,6 +233,51 @@ sync_chain138_public_rpc_env() {
|
||||
if [[ -n "${TOKEN_AGGREGATION_PMM_QUERY_TRADER:-}" ]]; then
|
||||
upsert_env_var "$env_file" "TOKEN_AGGREGATION_PMM_QUERY_TRADER" "$TOKEN_AGGREGATION_PMM_QUERY_TRADER"
|
||||
fi
|
||||
upsert_env_var "$env_file" "PORT" "$token_agg_port"
|
||||
}
|
||||
|
||||
sync_token_aggregation_database_env() {
|
||||
local env_file="$1"
|
||||
local db_url="${TOKEN_AGG_DATABASE_URL:-${DATABASE_URL:-}}"
|
||||
local dbis_primary="${DBIS_POSTGRES_PRIMARY:-192.168.11.105}"
|
||||
|
||||
if [[ -z "$db_url" ]]; then
|
||||
db_url="postgresql://dbis:8cba649443f97436db43b34ab2c0e75b5cf15611bef9c099cee6fb22cc3d7771@${dbis_primary}:5432/dbis_core?sslmode=disable"
|
||||
fi
|
||||
|
||||
upsert_env_var "$env_file" "DATABASE_URL" "$db_url"
|
||||
}
|
||||
|
||||
sync_chain138_native_v2_env() {
|
||||
local env_file="$1"
|
||||
local key=""
|
||||
local value=""
|
||||
local keys=(
|
||||
CHAIN_138_UNISWAP_V2_FACTORY
|
||||
CHAIN_138_UNISWAP_V2_ROUTER
|
||||
CHAIN_138_UNISWAP_V2_START_BLOCK
|
||||
CHAIN138_UNISWAP_V2_NATIVE_WETH_USDT_PAIR
|
||||
CHAIN138_UNISWAP_V2_NATIVE_WETH_USDC_PAIR
|
||||
CHAIN138_UNISWAP_V2_NATIVE_CUSDT_CUSDC_PAIR
|
||||
CHAIN_138_SUSHISWAP_FACTORY
|
||||
CHAIN_138_SUSHISWAP_ROUTER
|
||||
CHAIN_138_SUSHISWAP_START_BLOCK
|
||||
CHAIN138_SUSHISWAP_NATIVE_WETH_USDT_PAIR
|
||||
CHAIN138_SUSHISWAP_NATIVE_WETH_USDC_PAIR
|
||||
CHAIN138_SUSHISWAP_NATIVE_CUSDT_CUSDC_PAIR
|
||||
)
|
||||
|
||||
for key in "${keys[@]}"; do
|
||||
value="${!key:-}"
|
||||
if [[ -z "$value" ]]; then
|
||||
value="$(derive_repo_example_env_value "$key" || true)"
|
||||
fi
|
||||
if [[ -n "$value" ]]; then
|
||||
upsert_env_var "$env_file" "$key" "$value"
|
||||
else
|
||||
ensure_env_key "$env_file" "$key"
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
if [ ! -d "$SVC_DIR" ]; then
|
||||
@@ -259,6 +307,8 @@ fi
|
||||
|
||||
sync_gru_transport_env .env
|
||||
sync_chain138_public_rpc_env .env
|
||||
sync_token_aggregation_database_env .env
|
||||
sync_chain138_native_v2_env .env
|
||||
|
||||
if command -v pnpm >/dev/null 2>&1 && [ -f "$REPO_ROOT/pnpm-lock.yaml" ]; then
|
||||
(cd "$REPO_ROOT" && pnpm install --filter token-aggregation-service --no-frozen-lockfile 2>/dev/null) || true
|
||||
|
||||
@@ -1,171 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# Enable the upstream Blockscout smart-contract verifier sidecar on CT 5000 and
|
||||
# wire Blockscout to use it.
|
||||
#
|
||||
# Usage:
|
||||
# bash scripts/deployment/ensure-blockscout-smart-contract-verifier-5000.sh --dry-run
|
||||
# bash scripts/deployment/ensure-blockscout-smart-contract-verifier-5000.sh --apply
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)"
|
||||
|
||||
if [[ -f "${PROJECT_ROOT}/scripts/lib/load-project-env.sh" ]]; then
|
||||
# shellcheck source=/dev/null
|
||||
source "${PROJECT_ROOT}/scripts/lib/load-project-env.sh"
|
||||
fi
|
||||
|
||||
HOST="${PROXMOX_HOST_R630_02:-192.168.11.12}"
|
||||
VMID="${BLOCKSCOUT_DB_CT_VMID:-5000}"
|
||||
APPLY=0
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--apply) APPLY=1; shift ;;
|
||||
--dry-run) APPLY=0; shift ;;
|
||||
*)
|
||||
echo "Unknown argument: $1" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
read -r -d '' REMOTE_SCRIPT <<'EOF_REMOTE' || true
|
||||
set -euo pipefail
|
||||
cd /opt/blockscout
|
||||
|
||||
stamp="$(date +%Y%m%d_%H%M%S)"
|
||||
cp docker-compose.yml "docker-compose.yml.bak.${stamp}.pre_verifier"
|
||||
|
||||
cat > smart-contract-verifier.env <<'EOF_VERIFIER'
|
||||
SMART_CONTRACT_VERIFIER__SERVER__HTTP__ENABLED=true
|
||||
SMART_CONTRACT_VERIFIER__SERVER__HTTP__ADDR=0.0.0.0:8050
|
||||
SMART_CONTRACT_VERIFIER__SERVER__HTTP__MAX_BODY_SIZE=8388608
|
||||
SMART_CONTRACT_VERIFIER__SERVER__GRPC__ENABLED=false
|
||||
SMART_CONTRACT_VERIFIER__SERVER__GRPC__ADDR=0.0.0.0:8051
|
||||
SMART_CONTRACT_VERIFIER__SOLIDITY__ENABLED=true
|
||||
SMART_CONTRACT_VERIFIER__SOLIDITY__COMPILERS_DIR=/tmp/solidity-compilers
|
||||
SMART_CONTRACT_VERIFIER__SOLIDITY__REFRESH_VERSIONS_SCHEDULE=0 0 * * * * *
|
||||
SMART_CONTRACT_VERIFIER__SOLIDITY__FETCHER__LIST__LIST_URL=https://binaries.soliditylang.org/linux-amd64/list.json
|
||||
SMART_CONTRACT_VERIFIER__VYPER__ENABLED=true
|
||||
SMART_CONTRACT_VERIFIER__VYPER__COMPILERS_DIR=/tmp/vyper-compilers
|
||||
SMART_CONTRACT_VERIFIER__VYPER__REFRESH_VERSIONS_SCHEDULE=0 0 * * * * *
|
||||
SMART_CONTRACT_VERIFIER__VYPER__FETCHER__LIST__LIST_URL=https://raw.githubusercontent.com/blockscout/solc-bin/main/vyper.list.json
|
||||
SMART_CONTRACT_VERIFIER__SOURCIFY__ENABLED=true
|
||||
SMART_CONTRACT_VERIFIER__SOURCIFY__API_URL=https://sourcify.dev/server/
|
||||
SMART_CONTRACT_VERIFIER__SOURCIFY__VERIFICATION_ATTEMPTS=3
|
||||
SMART_CONTRACT_VERIFIER__SOURCIFY__REQUEST_TIMEOUT=10
|
||||
SMART_CONTRACT_VERIFIER__METRICS__ENABLED=false
|
||||
SMART_CONTRACT_VERIFIER__JAEGER__ENABLED=false
|
||||
EOF_VERIFIER
|
||||
|
||||
cat > docker-compose.yml <<'EOF_COMPOSE'
|
||||
version: "3.8"
|
||||
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:15-alpine
|
||||
container_name: blockscout-postgres
|
||||
environment:
|
||||
POSTGRES_USER: blockscout
|
||||
POSTGRES_PASSWORD: blockscout
|
||||
POSTGRES_DB: blockscout
|
||||
volumes:
|
||||
- postgres-data:/var/lib/postgresql/data
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- blockscout-network
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U blockscout"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
|
||||
smart-contract-verifier:
|
||||
image: ghcr.io/blockscout/smart-contract-verifier:latest
|
||||
container_name: smart-contract-verifier
|
||||
env_file:
|
||||
- ./smart-contract-verifier.env
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- blockscout-network
|
||||
|
||||
blockscout:
|
||||
image: blockscout/blockscout:latest
|
||||
container_name: blockscout
|
||||
command: bin/blockscout start
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
smart-contract-verifier:
|
||||
condition: service_started
|
||||
environment:
|
||||
- DISABLE_WEBAPP=false
|
||||
- DISABLE_INDEXER=false
|
||||
- INDEXER_DISABLE_PENDING_TRANSACTIONS_FETCHER=true
|
||||
- DATABASE_URL=postgresql://blockscout:blockscout@postgres:5432/blockscout?sslmode=disable
|
||||
- ETHEREUM_JSONRPC_HTTP_URL=http://192.168.11.221:8545
|
||||
- ETHEREUM_JSONRPC_WS_URL=ws://192.168.11.221:8546
|
||||
- ETHEREUM_JSONRPC_TRACE_URL=http://192.168.11.221:8545
|
||||
- ETHEREUM_JSONRPC_VARIANT=besu
|
||||
- CHAIN_ID=138
|
||||
- COIN=ETH
|
||||
- BLOCKSCOUT_HOST=explorer.d-bis.org
|
||||
- BLOCKSCOUT_PROTOCOL=https
|
||||
- SECRET_KEY_BASE=73159c7d10b9a5a75ddf10710773078c078bf02124d35b72fa2a841b30b4f88c7c43e5caaf7f9f7f87d16dd66e7870931ae11039c428d1dedae187af762531fa
|
||||
- POOL_SIZE=50
|
||||
- POOL_SIZE_API=50
|
||||
- DATABASE_QUEUE_TARGET=5s
|
||||
- ECTO_USE_SSL=false
|
||||
- MICROSERVICE_SC_VERIFIER_ENABLED=true
|
||||
- MICROSERVICE_SC_VERIFIER_TYPE=sc_verifier
|
||||
- MICROSERVICE_SC_VERIFIER_URL=http://smart-contract-verifier:8050/
|
||||
ports:
|
||||
- "4000:4000"
|
||||
volumes:
|
||||
- blockscout-data:/app/apps/explorer/priv/static
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- blockscout-network
|
||||
|
||||
volumes:
|
||||
postgres-data:
|
||||
blockscout-data:
|
||||
|
||||
networks:
|
||||
blockscout-network:
|
||||
driver: bridge
|
||||
EOF_COMPOSE
|
||||
|
||||
docker pull ghcr.io/blockscout/smart-contract-verifier:latest
|
||||
docker rm -f blockscout 2>/dev/null || true
|
||||
docker rm -f smart-contract-verifier 2>/dev/null || true
|
||||
# Older docker-compose v1 can leave an orphaned auto-generated verifier container
|
||||
# that breaks recreation with a `ContainerConfig` KeyError. Clear it first.
|
||||
docker ps -a --format '{{.Names}}' | grep -E 'smart-contract-verifier$' | xargs -r docker rm -f
|
||||
|
||||
if command -v docker-compose >/dev/null 2>&1; then
|
||||
docker-compose -f docker-compose.yml up -d
|
||||
else
|
||||
docker compose -f docker-compose.yml up -d
|
||||
fi
|
||||
|
||||
sleep 10
|
||||
docker ps --format "table {{.Names}}\t{{.Status}}"
|
||||
curl -fsS http://127.0.0.1:4000/api/v2/smart-contracts/verification/config >/dev/null
|
||||
EOF_REMOTE
|
||||
|
||||
echo "Blockscout smart-contract verifier enablement"
|
||||
echo "Host: ${HOST}"
|
||||
echo "VMID: ${VMID}"
|
||||
echo
|
||||
|
||||
if (( APPLY == 0 )); then
|
||||
echo "[dry-run] Would patch /opt/blockscout/docker-compose.yml on CT ${VMID}, add smart-contract-verifier, and restart Blockscout."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
ssh root@"${HOST}" "pct exec ${VMID} -- bash -lc $(printf '%q' "${REMOTE_SCRIPT}")"
|
||||
echo
|
||||
echo "[ok] Blockscout verifier sidecar enabled on CT ${VMID}."
|
||||
@@ -161,6 +161,11 @@ for prefix in "" "/token-aggregation"; do
|
||||
fi
|
||||
done
|
||||
|
||||
ROOT_V1_CODE=$(curl -sS -o /tmp/ta-root-v1.json -w "%{http_code}" -m 25 \
|
||||
"${BASE_URL}/api/v1/tokens?chainId=138&limit=1" 2>/dev/null || echo "000")
|
||||
TOKEN_AGG_V1_CODE=$(curl -sS -o /tmp/ta-tokenagg-v1.json -w "%{http_code}" -m 25 \
|
||||
"${BASE_URL}/token-aggregation/api/v1/tokens?chainId=138&limit=1" 2>/dev/null || echo "000")
|
||||
|
||||
echo ""
|
||||
echo "Notes:"
|
||||
echo " - Empty tokens/pools: set DATABASE_URL, then run bash smom-dbis-138/services/token-aggregation/scripts/apply-lightweight-schema.sh for standalone deployments; RPC to 138; PMM integration now defaults on-chain if env unset."
|
||||
@@ -169,17 +174,16 @@ echo " - bridge/preflight blocked pairs: run bash scripts/verify/check-gru-tran
|
||||
echo " - gas-registry 404: redeploy token-aggregation from repo (implements GET /api/v1/report/gas-registry)."
|
||||
echo " - Health: curl -s http://127.0.0.1:3001/health on explorer VM (not always proxied as /health)."
|
||||
echo " - planner-v2 publishes under /token-aggregation/api/v2/* so it does not collide with Blockscout /api/v2/* on explorer.d-bis.org."
|
||||
echo " - Apex https://explorer.d-bis.org/api/v1/* returns 400 while /token-aggregation/api/v1/* works: add HTTP+HTTPS location /api/v1/ → token-aggregation (scripts/fix-explorer-http-api-v1-proxy.sh on explorer VM)."
|
||||
if [[ "$ROOT_V1_CODE" == "400" ]]; then
|
||||
echo " - Apex https://explorer.d-bis.org/api/v1/* returns 400 while /token-aggregation/api/v1/* works: add HTTP+HTTPS location /api/v1/ → token-aggregation (scripts/fix-explorer-http-api-v1-proxy.sh on explorer VM)."
|
||||
elif [[ "$ROOT_V1_CODE" == "200" ]]; then
|
||||
echo " - Apex https://explorer.d-bis.org/api/v1/* is live and proxied to token-aggregation."
|
||||
fi
|
||||
echo " - If POST /token-aggregation/api/v2/* returns 405 or HTML instead of JSON, insert the v2 proxy block (scripts/fix-explorer-token-aggregation-api-v2-proxy.sh on VMID 5000)."
|
||||
echo " - Fresh binary + PMM env: bash scripts/deploy-token-aggregation-for-publication.sh then rsync dist/node_modules/.env to /opt/token-aggregation; systemctl restart token-aggregation (see TOKEN_AGGREGATION_REPORT_API_RUNBOOK.md)."
|
||||
echo " - DODO v3 pilot routes should return provider=dodo_v3, routePlanPresent=true, and an internal-execution-plan object targeting EnhancedSwapRouterV2 when CHAIN138_ENABLE_DODO_V3_EXECUTION is live."
|
||||
echo " - The funded canonical cUSDC/USDC DODO pool should report non-zero route-tree depth on Chain 138; if it falls back to near-zero TVL again, check the DODO valuation path and the canonical PMM integration address."
|
||||
|
||||
ROOT_V1_CODE=$(curl -sS -o /tmp/ta-root-v1.json -w "%{http_code}" -m 25 \
|
||||
"${BASE_URL}/api/v1/tokens?chainId=138&limit=1" 2>/dev/null || echo "000")
|
||||
TOKEN_AGG_V1_CODE=$(curl -sS -o /tmp/ta-tokenagg-v1.json -w "%{http_code}" -m 25 \
|
||||
"${BASE_URL}/token-aggregation/api/v1/tokens?chainId=138&limit=1" 2>/dev/null || echo "000")
|
||||
|
||||
echo ""
|
||||
echo "== summary =="
|
||||
echo "root_v1=$ROOT_V1_CODE token_aggregation_v1=$TOKEN_AGG_V1_CODE v2_caps=$V2_CAPS_CODE v2_plan=$V2_PLAN_CODE v2_internal_plan=$V2_INTERNAL_PLAN_CODE"
|
||||
|
||||
@@ -1,224 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# Verify the Chain 138 native Uniswap v2 and SushiSwap deployments on Blockscout.
|
||||
#
|
||||
# Usage:
|
||||
# bash scripts/verify/verify-chain138-native-v2-blockscout.sh
|
||||
# bash scripts/verify/verify-chain138-native-v2-blockscout.sh --status-only
|
||||
# bash scripts/verify/verify-chain138-native-v2-blockscout.sh --only UniswapV2Factory,SushiSwapRouter
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)"
|
||||
SMOM_ROOT="${PROJECT_ROOT}/smom-dbis-138"
|
||||
|
||||
if [[ -f "${PROJECT_ROOT}/scripts/lib/load-project-env.sh" ]]; then
|
||||
# shellcheck source=/dev/null
|
||||
source "${PROJECT_ROOT}/scripts/lib/load-project-env.sh"
|
||||
fi
|
||||
|
||||
command -v forge >/dev/null 2>&1 || { echo "ERROR: forge not found"; exit 1; }
|
||||
command -v node >/dev/null 2>&1 || { echo "ERROR: node not found"; exit 1; }
|
||||
command -v cast >/dev/null 2>&1 || { echo "ERROR: cast not found"; exit 1; }
|
||||
command -v jq >/dev/null 2>&1 || { echo "ERROR: jq not found"; exit 1; }
|
||||
command -v curl >/dev/null 2>&1 || { echo "ERROR: curl not found"; exit 1; }
|
||||
|
||||
RPC_URL="${RPC_URL_138:-${CHAIN138_RPC_URL:-http://192.168.11.211:8545}}"
|
||||
BLOCKSCOUT_URL="${CHAIN138_BLOCKSCOUT_INTERNAL_URL:-http://${IP_BLOCKSCOUT:-192.168.11.140}:4000}"
|
||||
BLOCKSCOUT_API_BASE="${CHAIN138_BLOCKSCOUT_API_BASE:-${BLOCKSCOUT_URL}/api/v2}"
|
||||
BLOCKSCOUT_PUBLIC_API_BASE="${CHAIN138_BLOCKSCOUT_PUBLIC_API_BASE:-https://explorer.d-bis.org/api/v2}"
|
||||
VERIFIER_PORT="${FORGE_VERIFIER_PROXY_PORT:-3080}"
|
||||
FORGE_VERIFIER_URL="${FORGE_VERIFIER_URL:-http://127.0.0.1:${VERIFIER_PORT}/api}"
|
||||
|
||||
UNISWAP_JSON="${SMOM_ROOT}/deployments/chain138/uniswap-v2-native.json"
|
||||
SUSHI_JSON="${SMOM_ROOT}/deployments/chain138/sushiswap-native.json"
|
||||
|
||||
ONLY_LIST=""
|
||||
STATUS_ONLY=0
|
||||
PROXY_PID=""
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--only) ONLY_LIST="${2:-}"; shift 2 ;;
|
||||
--status-only) STATUS_ONLY=1; shift ;;
|
||||
*)
|
||||
echo "Unknown argument: $1" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
cleanup_proxy() {
|
||||
[[ -n "${PROXY_PID:-}" ]] && kill "${PROXY_PID}" 2>/dev/null || true
|
||||
}
|
||||
trap cleanup_proxy EXIT
|
||||
|
||||
should_handle() {
|
||||
local name="$1"
|
||||
[[ -n "${ONLY_LIST}" ]] && [[ ",${ONLY_LIST}," != *",${name},"* ]] && return 1
|
||||
return 0
|
||||
}
|
||||
|
||||
log() { printf '%s\n' "$*"; }
|
||||
ok() { printf '[ok] %s\n' "$*"; }
|
||||
warn() { printf '[warn] %s\n' "$*" >&2; }
|
||||
fail() { printf '[fail] %s\n' "$*" >&2; exit 1; }
|
||||
|
||||
proxy_listening() {
|
||||
if command -v nc >/dev/null 2>&1; then
|
||||
nc -z -w 2 127.0.0.1 "${VERIFIER_PORT}" 2>/dev/null
|
||||
else
|
||||
timeout 2 bash -c "echo >/dev/tcp/127.0.0.1/${VERIFIER_PORT}" 2>/dev/null
|
||||
fi
|
||||
}
|
||||
|
||||
start_proxy_if_needed() {
|
||||
if proxy_listening; then
|
||||
ok "Forge verification proxy already listening on ${VERIFIER_PORT}."
|
||||
return 0
|
||||
fi
|
||||
|
||||
log "Starting forge verification proxy on ${VERIFIER_PORT} -> ${BLOCKSCOUT_URL}"
|
||||
PORT="${VERIFIER_PORT}" BLOCKSCOUT_URL="${BLOCKSCOUT_URL}" node "${PROJECT_ROOT}/forge-verification-proxy/server.js" >/tmp/chain138-native-v2-blockscout-proxy.log 2>&1 &
|
||||
PROXY_PID=$!
|
||||
sleep 2
|
||||
proxy_listening || fail "Forge verification proxy failed to start. See /tmp/chain138-native-v2-blockscout-proxy.log"
|
||||
}
|
||||
|
||||
has_contract_bytecode() {
|
||||
local addr="$1"
|
||||
local code
|
||||
code="$(cast code "${addr}" --rpc-url "${RPC_URL}" 2>/dev/null | tr -d '\n\r \t' | tr '[:upper:]' '[:lower:]')" || true
|
||||
[[ -n "${code}" && "${code}" != "0x" && "${code}" != "0x0" ]]
|
||||
}
|
||||
|
||||
verification_status_json() {
|
||||
local addr="$1"
|
||||
local raw
|
||||
local base
|
||||
for base in "${BLOCKSCOUT_API_BASE}" "${BLOCKSCOUT_PUBLIC_API_BASE}"; do
|
||||
raw="$(curl --max-time 20 -fsS "${base}/smart-contracts/${addr}" 2>/dev/null || true)"
|
||||
if [[ -n "${raw}" ]] && jq -e 'type == "object"' >/dev/null 2>&1 <<<"${raw}"; then
|
||||
printf '%s' "${raw}"
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
is_verified() {
|
||||
local addr="$1"
|
||||
local expected_name="$2"
|
||||
local json name compiler
|
||||
json="$(verification_status_json "${addr}")" || return 1
|
||||
name="$(jq -r '.name // empty' <<<"${json}")"
|
||||
compiler="$(jq -r '.compiler_version // empty' <<<"${json}")"
|
||||
[[ -n "${name}" && -n "${compiler}" && "${name}" == "${expected_name}" ]]
|
||||
}
|
||||
|
||||
submit_verification() {
|
||||
local label="$1"
|
||||
local addr="$2"
|
||||
local path="$3"
|
||||
local expected_name="$4"
|
||||
local constructor_sig="$5"
|
||||
local compiler_version="$6"
|
||||
local force_flag="${7:-0}"
|
||||
shift 6
|
||||
local constructor_args=("$@")
|
||||
|
||||
start_proxy_if_needed
|
||||
has_contract_bytecode "${addr}" || fail "${label} has no bytecode at ${addr}"
|
||||
|
||||
if is_verified "${addr}" "${expected_name}"; then
|
||||
ok "${label} already verified on Blockscout."
|
||||
return 0
|
||||
fi
|
||||
|
||||
local cmd=(forge verify-contract "${addr}" "${path}" --chain-id 138 --verifier blockscout --verifier-url "${FORGE_VERIFIER_URL}" --rpc-url "${RPC_URL}" --flatten)
|
||||
[[ -n "${compiler_version}" ]] && cmd+=(--compiler-version "${compiler_version}")
|
||||
if [[ "${force_flag}" == "1" ]]; then
|
||||
cmd+=(--force)
|
||||
fi
|
||||
if [[ -n "${constructor_sig}" ]]; then
|
||||
local encoded
|
||||
encoded="$(cast abi-encode "${constructor_sig}" "${constructor_args[@]:1}")"
|
||||
cmd+=(--constructor-args "${encoded}")
|
||||
fi
|
||||
|
||||
log "Submitting Blockscout verification for ${label} (${addr})"
|
||||
if (cd "${SMOM_ROOT}" && "${cmd[@]}" 2>&1); then
|
||||
ok "${label} verification submission accepted."
|
||||
else
|
||||
warn "${label} verification submission did not complete cleanly. Check Blockscout manually."
|
||||
fi
|
||||
}
|
||||
|
||||
[[ -f "${UNISWAP_JSON}" ]] || fail "Missing deployment artifact ${UNISWAP_JSON}"
|
||||
[[ -f "${SUSHI_JSON}" ]] || fail "Missing deployment artifact ${SUSHI_JSON}"
|
||||
|
||||
UNISWAP_FACTORY="$(jq -r '.factory' "${UNISWAP_JSON}")"
|
||||
UNISWAP_ROUTER="$(jq -r '.router' "${UNISWAP_JSON}")"
|
||||
UNISWAP_WETH="$(jq -r '.weth' "${UNISWAP_JSON}")"
|
||||
UNISWAP_DEPLOYER="$(jq -r '.deployer' "${UNISWAP_JSON}")"
|
||||
|
||||
SUSHI_FACTORY="$(jq -r '.factory' "${SUSHI_JSON}")"
|
||||
SUSHI_ROUTER="$(jq -r '.router' "${SUSHI_JSON}")"
|
||||
SUSHI_WETH="$(jq -r '.weth' "${SUSHI_JSON}")"
|
||||
SUSHI_FEE_TO_SETTER="$(jq -r '.feeToSetter // .deployer' "${SUSHI_JSON}")"
|
||||
|
||||
log "Chain 138 native V2 Blockscout verification"
|
||||
log "RPC: ${RPC_URL}"
|
||||
log "Explorer API: ${BLOCKSCOUT_API_BASE}"
|
||||
log
|
||||
|
||||
if (( STATUS_ONLY )); then
|
||||
should_handle "UniswapV2Factory" && { is_verified "${UNISWAP_FACTORY}" "UniswapV2Factory" && ok "UniswapV2Factory already verified on Blockscout." || warn "UniswapV2Factory not yet verified on Blockscout."; }
|
||||
should_handle "UniswapV2Router" && { is_verified "${UNISWAP_ROUTER}" "UniswapV2Router02" && ok "UniswapV2Router already verified on Blockscout." || warn "UniswapV2Router not yet verified on Blockscout."; }
|
||||
should_handle "SushiSwapFactory" && { is_verified "${SUSHI_FACTORY}" "UniswapV2Factory" && ok "SushiSwapFactory already verified on Blockscout." || warn "SushiSwapFactory not yet verified on Blockscout."; }
|
||||
should_handle "SushiSwapRouter" && { is_verified "${SUSHI_ROUTER}" "UniswapV2Router02" && ok "SushiSwapRouter already verified on Blockscout." || warn "SushiSwapRouter not yet verified on Blockscout."; }
|
||||
exit 0
|
||||
fi
|
||||
|
||||
should_handle "UniswapV2Factory" && submit_verification \
|
||||
"UniswapV2Factory" \
|
||||
"${UNISWAP_FACTORY}" \
|
||||
"contracts/vendor/uniswap-v2-core/UniswapV2Factory.sol:UniswapV2Factory" \
|
||||
"UniswapV2Factory" \
|
||||
"constructor(address)" \
|
||||
"v0.5.16+commit.9c3226ce" \
|
||||
"1" \
|
||||
"${UNISWAP_DEPLOYER}"
|
||||
|
||||
should_handle "UniswapV2Router" && submit_verification \
|
||||
"UniswapV2Router" \
|
||||
"${UNISWAP_ROUTER}" \
|
||||
"contracts/vendor/uniswap-v2-periphery/UniswapV2Router02.sol:UniswapV2Router02" \
|
||||
"UniswapV2Router02" \
|
||||
"constructor(address,address)" \
|
||||
"v0.6.6+commit.6c089d02" \
|
||||
"1" \
|
||||
"${UNISWAP_FACTORY}" "${UNISWAP_WETH}"
|
||||
|
||||
should_handle "SushiSwapFactory" && submit_verification \
|
||||
"SushiSwapFactory" \
|
||||
"${SUSHI_FACTORY}" \
|
||||
"contracts/vendor/sushiswap-v2/UniswapV2Factory.sol:UniswapV2Factory" \
|
||||
"UniswapV2Factory" \
|
||||
"constructor(address)" \
|
||||
"v0.6.12+commit.27d51765" \
|
||||
"1" \
|
||||
"${SUSHI_FEE_TO_SETTER}"
|
||||
|
||||
should_handle "SushiSwapRouter" && submit_verification \
|
||||
"SushiSwapRouter" \
|
||||
"${SUSHI_ROUTER}" \
|
||||
"contracts/vendor/sushiswap-v2/UniswapV2Router02.sol:UniswapV2Router02" \
|
||||
"UniswapV2Router02" \
|
||||
"constructor(address,address)" \
|
||||
"v0.6.12+commit.27d51765" \
|
||||
"1" \
|
||||
"${SUSHI_FACTORY}" "${SUSHI_WETH}"
|
||||
|
||||
log
|
||||
ok "Chain 138 native V2 verification flow complete."
|
||||
@@ -1,545 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# Verify the deployed Chain 138 route execution stack and pilot venue contracts on Blockscout.
|
||||
#
|
||||
# Usage:
|
||||
# bash scripts/verify/verify-chain138-route-execution-stack-blockscout.sh
|
||||
# bash scripts/verify/verify-chain138-route-execution-stack-blockscout.sh --status-only
|
||||
# bash scripts/verify/verify-chain138-route-execution-stack-blockscout.sh --no-wait
|
||||
# bash scripts/verify/verify-chain138-route-execution-stack-blockscout.sh --only EnhancedSwapRouterV2,Chain138PilotCurve3Pool
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)"
|
||||
SMOM_ROOT="${PROJECT_ROOT}/smom-dbis-138"
|
||||
|
||||
if [[ -f "${PROJECT_ROOT}/scripts/lib/load-project-env.sh" ]]; then
|
||||
# shellcheck source=/dev/null
|
||||
source "${PROJECT_ROOT}/scripts/lib/load-project-env.sh"
|
||||
fi
|
||||
|
||||
command -v forge >/dev/null 2>&1 || { echo "ERROR: forge not found"; exit 1; }
|
||||
command -v node >/dev/null 2>&1 || { echo "ERROR: node not found"; exit 1; }
|
||||
command -v cast >/dev/null 2>&1 || { echo "ERROR: cast not found"; exit 1; }
|
||||
command -v jq >/dev/null 2>&1 || { echo "ERROR: jq not found"; exit 1; }
|
||||
command -v curl >/dev/null 2>&1 || { echo "ERROR: curl not found"; exit 1; }
|
||||
|
||||
RPC_URL="${RPC_URL_138:-${CHAIN138_RPC_URL:-http://192.168.11.211:8545}}"
|
||||
BLOCKSCOUT_URL="${CHAIN138_BLOCKSCOUT_INTERNAL_URL:-http://${IP_BLOCKSCOUT:-192.168.11.140}:4000}"
|
||||
BLOCKSCOUT_API_BASE="${CHAIN138_BLOCKSCOUT_API_BASE:-${BLOCKSCOUT_URL}/api/v2}"
|
||||
BLOCKSCOUT_PUBLIC_API_BASE="${CHAIN138_BLOCKSCOUT_PUBLIC_API_BASE:-https://explorer.d-bis.org/api/v2}"
|
||||
VERIFIER_PORT="${FORGE_VERIFIER_PROXY_PORT:-3080}"
|
||||
FORGE_VERIFIER_URL="${FORGE_VERIFIER_URL:-http://127.0.0.1:${VERIFIER_PORT}/api}"
|
||||
ROUTE_STACK_SOLC_VERSION="${ROUTE_STACK_SOLC_VERSION:-v0.8.20+commit.a1b79de6}"
|
||||
ROUTE_STACK_EVM_VERSION="${ROUTE_STACK_EVM_VERSION:-shanghai}"
|
||||
ROUTE_STACK_OPT_RUNS="${ROUTE_STACK_OPT_RUNS:-200}"
|
||||
|
||||
ONLY_LIST=""
|
||||
STATUS_ONLY=0
|
||||
NO_WAIT=0
|
||||
PROXY_PID=""
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--only) ONLY_LIST="${2:-}"; shift 2 ;;
|
||||
--status-only) STATUS_ONLY=1; shift ;;
|
||||
--no-wait) NO_WAIT=1; shift ;;
|
||||
*)
|
||||
echo "Unknown argument: $1" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
cleanup_proxy() {
|
||||
[[ -n "${PROXY_PID:-}" ]] && kill "${PROXY_PID}" 2>/dev/null || true
|
||||
}
|
||||
trap cleanup_proxy EXIT
|
||||
|
||||
should_handle() {
|
||||
local name="$1"
|
||||
[[ -n "${ONLY_LIST}" ]] && [[ ",${ONLY_LIST}," != *",${name},"* ]] && return 1
|
||||
return 0
|
||||
}
|
||||
|
||||
log() { printf '%s\n' "$*"; }
|
||||
ok() { printf '[ok] %s\n' "$*"; }
|
||||
warn() { printf '[warn] %s\n' "$*" >&2; }
|
||||
fail() { printf '[fail] %s\n' "$*" >&2; exit 1; }
|
||||
|
||||
proxy_listening() {
|
||||
if command -v nc >/dev/null 2>&1; then
|
||||
nc -z -w 2 127.0.0.1 "${VERIFIER_PORT}" 2>/dev/null
|
||||
else
|
||||
timeout 2 bash -c "echo >/dev/tcp/127.0.0.1/${VERIFIER_PORT}" 2>/dev/null
|
||||
fi
|
||||
}
|
||||
|
||||
start_proxy_if_needed() {
|
||||
if proxy_listening; then
|
||||
ok "Forge verification proxy already listening on ${VERIFIER_PORT}."
|
||||
return 0
|
||||
fi
|
||||
|
||||
log "Starting forge verification proxy on ${VERIFIER_PORT} -> ${BLOCKSCOUT_URL}"
|
||||
PORT="${VERIFIER_PORT}" BLOCKSCOUT_URL="${BLOCKSCOUT_URL}" node "${PROJECT_ROOT}/forge-verification-proxy/server.js" >/tmp/chain138-route-execution-blockscout-proxy.log 2>&1 &
|
||||
PROXY_PID=$!
|
||||
sleep 2
|
||||
proxy_listening || fail "Forge verification proxy failed to start. See /tmp/chain138-route-execution-blockscout-proxy.log"
|
||||
}
|
||||
|
||||
has_contract_bytecode() {
|
||||
local addr="$1"
|
||||
local code
|
||||
code="$(cast code "${addr}" --rpc-url "${RPC_URL}" 2>/dev/null | tr -d '\n\r \t' | tr '[:upper:]' '[:lower:]')" || true
|
||||
[[ -n "${code}" && "${code}" != "0x" && "${code}" != "0x0" ]]
|
||||
}
|
||||
|
||||
verification_status_json() {
|
||||
local addr="$1"
|
||||
local raw
|
||||
local base
|
||||
for base in "${BLOCKSCOUT_API_BASE}" "${BLOCKSCOUT_PUBLIC_API_BASE}"; do
|
||||
raw="$(curl --max-time 20 -fsS "${base}/smart-contracts/${addr}" 2>/dev/null || true)"
|
||||
if [[ -n "${raw}" ]] && jq -e 'type == "object"' >/dev/null 2>&1 <<<"${raw}"; then
|
||||
printf '%s' "${raw}"
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
is_verified() {
|
||||
local addr="$1"
|
||||
local expected_name="$2"
|
||||
local json name compiler
|
||||
json="$(verification_status_json "${addr}")" || return 1
|
||||
name="$(jq -r '.name // empty' <<<"${json}")"
|
||||
compiler="$(jq -r '.compiler_version // empty' <<<"${json}")"
|
||||
[[ -n "${name}" && -n "${compiler}" && "${name}" == "${expected_name}" ]]
|
||||
}
|
||||
|
||||
submit_verification() {
|
||||
local label="$1"
|
||||
local addr="$2"
|
||||
local path="$3"
|
||||
local expected_name="$4"
|
||||
local constructor_sig="$5"
|
||||
shift 5
|
||||
local constructor_args=("$@")
|
||||
|
||||
start_proxy_if_needed
|
||||
has_contract_bytecode "${addr}" || fail "${label} has no bytecode at ${addr}"
|
||||
|
||||
if is_verified "${addr}" "${expected_name}"; then
|
||||
ok "${label} already verified on Blockscout."
|
||||
return 0
|
||||
fi
|
||||
|
||||
local cmd=(forge verify-contract "${addr}" "${path}" --chain-id 138 --verifier blockscout --verifier-url "${FORGE_VERIFIER_URL}" --rpc-url "${RPC_URL}" --flatten)
|
||||
if [[ -n "${constructor_sig}" ]]; then
|
||||
local encoded
|
||||
encoded="$(cast abi-encode "${constructor_sig}" "${constructor_args[@]}")"
|
||||
cmd+=(--constructor-args "${encoded}")
|
||||
fi
|
||||
|
||||
log "Submitting Blockscout verification for ${label} (${addr})"
|
||||
if (cd "${SMOM_ROOT}" && "${cmd[@]}" 2>&1); then
|
||||
ok "${label} verification submission accepted."
|
||||
else
|
||||
warn "${label} verification submission did not complete cleanly. Check Blockscout manually."
|
||||
fi
|
||||
}
|
||||
|
||||
artifact_dbg_path() {
|
||||
case "$1" in
|
||||
EnhancedSwapRouterV2) printf '%s' "${SMOM_ROOT}/artifacts/contracts/bridge/trustless/EnhancedSwapRouterV2.sol/EnhancedSwapRouterV2.dbg.json" ;;
|
||||
IntentBridgeCoordinatorV2) printf '%s' "${SMOM_ROOT}/artifacts/contracts/bridge/trustless/IntentBridgeCoordinatorV2.sol/IntentBridgeCoordinatorV2.dbg.json" ;;
|
||||
DodoRouteExecutorAdapter) printf '%s' "${SMOM_ROOT}/artifacts/contracts/bridge/trustless/adapters/DodoRouteExecutorAdapter.sol/DodoRouteExecutorAdapter.dbg.json" ;;
|
||||
DodoV3RouteExecutorAdapter) printf '%s' "${SMOM_ROOT}/artifacts/contracts/bridge/trustless/adapters/DodoV3RouteExecutorAdapter.sol/DodoV3RouteExecutorAdapter.dbg.json" ;;
|
||||
UniswapV3RouteExecutorAdapter) printf '%s' "${SMOM_ROOT}/artifacts/contracts/bridge/trustless/adapters/UniswapV3RouteExecutorAdapter.sol/UniswapV3RouteExecutorAdapter.dbg.json" ;;
|
||||
BalancerRouteExecutorAdapter) printf '%s' "${SMOM_ROOT}/artifacts/contracts/bridge/trustless/adapters/BalancerRouteExecutorAdapter.sol/BalancerRouteExecutorAdapter.dbg.json" ;;
|
||||
CurveRouteExecutorAdapter) printf '%s' "${SMOM_ROOT}/artifacts/contracts/bridge/trustless/adapters/CurveRouteExecutorAdapter.sol/CurveRouteExecutorAdapter.dbg.json" ;;
|
||||
OneInchRouteExecutorAdapter) printf '%s' "${SMOM_ROOT}/artifacts/contracts/bridge/trustless/adapters/OneInchRouteExecutorAdapter.sol/OneInchRouteExecutorAdapter.dbg.json" ;;
|
||||
Chain138PilotUniswapV3Router) printf '%s' "${SMOM_ROOT}/artifacts/contracts/bridge/trustless/pilot/Chain138PilotDexVenues.sol/Chain138PilotUniswapV3Router.dbg.json" ;;
|
||||
Chain138PilotBalancerVault) printf '%s' "${SMOM_ROOT}/artifacts/contracts/bridge/trustless/pilot/Chain138PilotDexVenues.sol/Chain138PilotBalancerVault.dbg.json" ;;
|
||||
Chain138PilotCurve3Pool) printf '%s' "${SMOM_ROOT}/artifacts/contracts/bridge/trustless/pilot/Chain138PilotDexVenues.sol/Chain138PilotCurve3Pool.dbg.json" ;;
|
||||
Chain138PilotOneInchAggregationRouter) printf '%s' "${SMOM_ROOT}/artifacts/contracts/bridge/trustless/pilot/Chain138PilotDexVenues.sol/Chain138PilotOneInchAggregationRouter.dbg.json" ;;
|
||||
*) return 1 ;;
|
||||
esac
|
||||
}
|
||||
|
||||
foundry_artifact_json_path() {
|
||||
case "$1" in
|
||||
EnhancedSwapRouterV2) printf '%s' "${SMOM_ROOT}/out/EnhancedSwapRouterV2.sol/EnhancedSwapRouterV2.json" ;;
|
||||
IntentBridgeCoordinatorV2) printf '%s' "${SMOM_ROOT}/out/IntentBridgeCoordinatorV2.sol/IntentBridgeCoordinatorV2.json" ;;
|
||||
DodoRouteExecutorAdapter) printf '%s' "${SMOM_ROOT}/out/DodoRouteExecutorAdapter.sol/DodoRouteExecutorAdapter.json" ;;
|
||||
DodoV3RouteExecutorAdapter) printf '%s' "${SMOM_ROOT}/out/DodoV3RouteExecutorAdapter.sol/DodoV3RouteExecutorAdapter.json" ;;
|
||||
UniswapV3RouteExecutorAdapter) printf '%s' "${SMOM_ROOT}/out/UniswapV3RouteExecutorAdapter.sol/UniswapV3RouteExecutorAdapter.json" ;;
|
||||
BalancerRouteExecutorAdapter) printf '%s' "${SMOM_ROOT}/out/BalancerRouteExecutorAdapter.sol/BalancerRouteExecutorAdapter.json" ;;
|
||||
CurveRouteExecutorAdapter) printf '%s' "${SMOM_ROOT}/out/CurveRouteExecutorAdapter.sol/CurveRouteExecutorAdapter.json" ;;
|
||||
OneInchRouteExecutorAdapter) printf '%s' "${SMOM_ROOT}/out/OneInchRouteExecutorAdapter.sol/OneInchRouteExecutorAdapter.json" ;;
|
||||
Chain138PilotUniswapV3Router|Chain138PilotBalancerVault|Chain138PilotCurve3Pool|Chain138PilotOneInchAggregationRouter)
|
||||
printf '%s' "${SMOM_ROOT}/out/Chain138PilotDexVenues.sol/${1}.json"
|
||||
;;
|
||||
*) return 1 ;;
|
||||
esac
|
||||
}
|
||||
|
||||
runtime_hash_report() {
|
||||
local label="$1"
|
||||
local addr="$2"
|
||||
local artifact_json artifact_runtime chain_runtime
|
||||
|
||||
artifact_json="$(foundry_artifact_json_path "${label}")" || return 0
|
||||
[[ -f "${artifact_json}" ]] || return 0
|
||||
|
||||
artifact_runtime="$(jq -r '.deployedBytecode.object // empty' "${artifact_json}" | tr '[:upper:]' '[:lower:]')"
|
||||
chain_runtime="$(cast code "${addr}" --rpc-url "${RPC_URL}" 2>/dev/null | tr -d '\n\r \t' | tr '[:upper:]' '[:lower:]')" || true
|
||||
|
||||
[[ -n "${artifact_runtime}" && -n "${chain_runtime}" && "${chain_runtime}" != "0x" ]] || return 0
|
||||
|
||||
if [[ "${artifact_runtime}" != "${chain_runtime}" ]]; then
|
||||
warn "${label}: Foundry artifact runtime bytecode does not match deployed bytecode."
|
||||
warn "${label}: artifact_keccak=$(cast keccak "${artifact_runtime}") chain_keccak=$(cast keccak "${chain_runtime}")"
|
||||
fi
|
||||
}
|
||||
|
||||
submit_standard_input_from_artifact() {
|
||||
local label="$1"
|
||||
local addr="$2"
|
||||
local contract_path="$3"
|
||||
local constructor_args="$4"
|
||||
local dbg build input_file compiler_version evm_version optimization_runs optimization_enabled license_type response message
|
||||
|
||||
dbg="$(artifact_dbg_path "${label}")" || fail "${label}: missing dbg path mapping"
|
||||
[[ -f "${dbg}" ]] || fail "${label}: missing dbg artifact ${dbg}"
|
||||
build="$(jq -r '.buildInfo // .build_info // empty' "${dbg}")"
|
||||
[[ -n "${build}" && "${build}" != "null" ]] || fail "${label}: missing build-info reference in ${dbg}"
|
||||
build="$(cd "$(dirname "${dbg}")" && realpath "${build}")"
|
||||
[[ -f "${build}" ]] || fail "${label}: missing build-info file ${build}"
|
||||
|
||||
input_file="$(mktemp)"
|
||||
python3 - "${dbg}" "${build}" "${input_file}" "${contract_path%%:*}" <<'PY'
|
||||
import json
|
||||
import os
|
||||
import posixpath
|
||||
import re
|
||||
import sys
|
||||
|
||||
dbg_path, build_path, out_path, fallback_source = sys.argv[1:5]
|
||||
with open(dbg_path, "r", encoding="utf-8") as fh:
|
||||
dbg = json.load(fh)
|
||||
with open(build_path, "r", encoding="utf-8") as fh:
|
||||
build = json.load(fh)
|
||||
|
||||
source_name = dbg.get("sourceName") or dbg.get("source_name") or fallback_source
|
||||
if not source_name:
|
||||
raise SystemExit(f"missing sourceName in {dbg_path}")
|
||||
|
||||
input_data = build["input"]
|
||||
sources = input_data.get("sources", {})
|
||||
if source_name not in sources:
|
||||
raise SystemExit(f"source {source_name} missing from build-info input")
|
||||
|
||||
import_re = re.compile(r'import\s+(?:[^;]*?\s+from\s+)?["\']([^"\']+)["\']\s*;')
|
||||
|
||||
closure = set()
|
||||
stack = [source_name]
|
||||
while stack:
|
||||
current = stack.pop()
|
||||
if current in closure or current not in sources:
|
||||
continue
|
||||
closure.add(current)
|
||||
content = sources[current].get("content", "")
|
||||
for entry in import_re.findall(content):
|
||||
if entry.startswith("."):
|
||||
target = posixpath.normpath(posixpath.join(posixpath.dirname(current), entry))
|
||||
else:
|
||||
target = entry
|
||||
if target in sources and target not in closure:
|
||||
stack.append(target)
|
||||
|
||||
reduced = json.loads(json.dumps(input_data))
|
||||
reduced["sources"] = {name: sources[name] for name in sorted(closure)}
|
||||
|
||||
with open(out_path, "w", encoding="utf-8") as fh:
|
||||
json.dump(reduced, fh, separators=(",", ":"))
|
||||
PY
|
||||
compiler_version="$(jq -r '.solcLongVersion | "v" + .' "${build}")"
|
||||
evm_version="$(jq -r '.input.settings.evmVersion // "default"' "${build}")"
|
||||
optimization_runs="$(jq -r '.input.settings.optimizer.runs // 200' "${build}")"
|
||||
optimization_enabled="$(jq -r '.input.settings.optimizer.enabled // true' "${build}")"
|
||||
license_type="mit"
|
||||
|
||||
response="$(
|
||||
curl --max-time 180 -fsS -X POST \
|
||||
-F "compiler_version=${compiler_version}" \
|
||||
-F "contract_name=${contract_path}" \
|
||||
-F "autodetect_constructor_args=false" \
|
||||
-F "constructor_args=${constructor_args}" \
|
||||
-F "optimization_runs=${optimization_runs}" \
|
||||
-F "is_optimization_enabled=${optimization_enabled}" \
|
||||
-F "evm_version=${evm_version}" \
|
||||
-F "license_type=${license_type}" \
|
||||
-F "files[0]=@${input_file};type=application/json" \
|
||||
"${BLOCKSCOUT_URL}/api/v2/smart-contracts/${addr}/verification/via/standard-input"
|
||||
)" || {
|
||||
rm -f "${input_file}"
|
||||
fail "${label}: Blockscout standard-input submission failed."
|
||||
}
|
||||
rm -f "${input_file}"
|
||||
|
||||
message="$(jq -r '.message // empty' <<<"${response}")"
|
||||
if [[ "${message}" == "Smart-contract verification started" ]]; then
|
||||
ok "${label} standard-input verification submission accepted."
|
||||
return 0
|
||||
fi
|
||||
|
||||
warn "${label} standard-input verification returned: ${response}"
|
||||
return 1
|
||||
}
|
||||
|
||||
submit_standard_input_from_forge() {
|
||||
local label="$1"
|
||||
local addr="$2"
|
||||
local contract_path="$3"
|
||||
local constructor_args="$4"
|
||||
local input_file response message
|
||||
local compiler_version evm_version optimization_runs via_ir_flag artifact_json
|
||||
|
||||
compiler_version="${ROUTE_STACK_SOLC_VERSION}"
|
||||
evm_version="${ROUTE_STACK_EVM_VERSION}"
|
||||
optimization_runs="${ROUTE_STACK_OPT_RUNS}"
|
||||
via_ir_flag=(--via-ir)
|
||||
|
||||
artifact_json="$(foundry_artifact_json_path "${label}" || true)"
|
||||
if [[ -n "${artifact_json}" && -f "${artifact_json}" ]]; then
|
||||
compiler_version="v$(jq -r '.metadata.compiler.version // empty' "${artifact_json}")"
|
||||
evm_version="$(jq -r '.metadata.settings.evmVersion // "default"' "${artifact_json}")"
|
||||
optimization_runs="$(jq -r '.metadata.settings.optimizer.runs // 200' "${artifact_json}")"
|
||||
if [[ "$(jq -r '.metadata.settings.viaIR // false' "${artifact_json}")" != "true" ]]; then
|
||||
via_ir_flag=()
|
||||
fi
|
||||
fi
|
||||
|
||||
runtime_hash_report "${label}" "${addr}"
|
||||
input_file="$(mktemp)"
|
||||
(
|
||||
cd "${SMOM_ROOT}"
|
||||
forge verify-contract "${addr}" "${contract_path}" \
|
||||
--chain-id 138 \
|
||||
--root . \
|
||||
--compiler-version "${compiler_version}" \
|
||||
--num-of-optimizations "${optimization_runs}" \
|
||||
"${via_ir_flag[@]}" \
|
||||
--evm-version "${evm_version}" \
|
||||
--show-standard-json-input >"${input_file}"
|
||||
) || {
|
||||
rm -f "${input_file}"
|
||||
fail "${label}: failed to render Foundry standard-input from deployment sources."
|
||||
}
|
||||
|
||||
response="$(
|
||||
curl --max-time 180 -fsS -X POST \
|
||||
-F "compiler_version=${compiler_version}" \
|
||||
-F "contract_name=${contract_path}" \
|
||||
-F "autodetect_constructor_args=false" \
|
||||
-F "constructor_args=${constructor_args}" \
|
||||
-F "optimization_runs=${optimization_runs}" \
|
||||
-F "is_optimization_enabled=true" \
|
||||
-F "evm_version=${evm_version}" \
|
||||
-F "license_type=mit" \
|
||||
-F "files[0]=@${input_file};type=application/json" \
|
||||
"${BLOCKSCOUT_URL}/api/v2/smart-contracts/${addr}/verification/via/standard-input"
|
||||
)" || {
|
||||
rm -f "${input_file}"
|
||||
fail "${label}: Blockscout Foundry standard-input submission failed."
|
||||
}
|
||||
rm -f "${input_file}"
|
||||
|
||||
message="$(jq -r '.message // empty' <<<"${response}")"
|
||||
if [[ "${message}" == "Smart-contract verification started" ]]; then
|
||||
ok "${label} Foundry standard-input verification submission accepted."
|
||||
return 0
|
||||
fi
|
||||
|
||||
warn "${label} Foundry standard-input verification returned: ${response}"
|
||||
return 1
|
||||
}
|
||||
|
||||
submit_best_verification() {
|
||||
local label="$1"
|
||||
local addr="$2"
|
||||
local path="$3"
|
||||
local expected_name="$4"
|
||||
local constructor_sig="$5"
|
||||
shift 5
|
||||
local constructor_args=("$@")
|
||||
local encoded=""
|
||||
|
||||
if [[ -n "${constructor_sig}" ]]; then
|
||||
encoded="$(cast abi-encode "${constructor_sig}" "${constructor_args[@]}")"
|
||||
fi
|
||||
|
||||
# Prefer the Foundry deployment lineage for the route stack. The earlier
|
||||
# Hardhat dbg/build-info path drifted away from the actual deployed compiler/EVM
|
||||
# settings and is kept only as a compatibility fallback.
|
||||
if submit_standard_input_from_forge "${label}" "${addr}" "${path}" "${encoded}"; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
if artifact_dbg_path "${label}" >/dev/null 2>&1; then
|
||||
warn "${label}: falling back to artifact-derived standard-input after Foundry mismatch."
|
||||
submit_standard_input_from_artifact "${label}" "${addr}" "${path}" "${encoded}" || return 1
|
||||
return 0
|
||||
fi
|
||||
|
||||
warn "${label}: falling back to legacy Forge flattened verification path."
|
||||
submit_verification "${label}" "${addr}" "${path}" "${expected_name}" "${constructor_sig}" "${constructor_args[@]}"
|
||||
}
|
||||
|
||||
WETH="0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"
|
||||
USDT="0x004b63A7B5b0E06f6bB6adb4a5F9f590BF3182D1"
|
||||
USDC="0x71D6687F38b93CCad569Fa6352c876eea967201b"
|
||||
DAI_PLACEHOLDER="0x6B175474E89094C44Da98b954EedeAC495271d0F"
|
||||
|
||||
ROUTER_V2="$(jq -r '.chains["138"].contracts.EnhancedSwapRouterV2' "${PROJECT_ROOT}/config/smart-contracts-master.json")"
|
||||
COORDINATOR_V2="$(jq -r '.chains["138"].contracts.IntentBridgeCoordinatorV2' "${PROJECT_ROOT}/config/smart-contracts-master.json")"
|
||||
DODO_ADAPTER="$(jq -r '.chains["138"].contracts.DodoRouteExecutorAdapter' "${PROJECT_ROOT}/config/smart-contracts-master.json")"
|
||||
DODO_V3_ADAPTER="$(jq -r '.chains["138"].contracts.DodoV3RouteExecutorAdapter' "${PROJECT_ROOT}/config/smart-contracts-master.json")"
|
||||
UNISWAP_V3_ADAPTER="$(jq -r '.chains["138"].contracts.UniswapV3RouteExecutorAdapter' "${PROJECT_ROOT}/config/smart-contracts-master.json")"
|
||||
BALANCER_ADAPTER="$(jq -r '.chains["138"].contracts.BalancerRouteExecutorAdapter' "${PROJECT_ROOT}/config/smart-contracts-master.json")"
|
||||
CURVE_ADAPTER="$(jq -r '.chains["138"].contracts.CurveRouteExecutorAdapter' "${PROJECT_ROOT}/config/smart-contracts-master.json")"
|
||||
ONEINCH_ADAPTER="$(jq -r '.chains["138"].contracts.OneInchRouteExecutorAdapter' "${PROJECT_ROOT}/config/smart-contracts-master.json")"
|
||||
PILOT_UNISWAP="$(jq -r '.chains["138"].contracts.PilotUniswapV3Router' "${PROJECT_ROOT}/config/smart-contracts-master.json")"
|
||||
PILOT_BALANCER="$(jq -r '.chains["138"].contracts.PilotBalancerVault' "${PROJECT_ROOT}/config/smart-contracts-master.json")"
|
||||
PILOT_CURVE="$(jq -r '.chains["138"].contracts.PilotCurve3Pool' "${PROJECT_ROOT}/config/smart-contracts-master.json")"
|
||||
PILOT_ONEINCH="$(jq -r '.chains["138"].contracts.PilotOneInchRouter' "${PROJECT_ROOT}/config/smart-contracts-master.json")"
|
||||
|
||||
log "Chain 138 route execution stack Blockscout verification"
|
||||
log "RPC: ${RPC_URL}"
|
||||
log "Explorer API: ${BLOCKSCOUT_API_BASE}"
|
||||
log
|
||||
|
||||
if (( STATUS_ONLY )); then
|
||||
for pair in \
|
||||
"EnhancedSwapRouterV2:${ROUTER_V2}:EnhancedSwapRouterV2" \
|
||||
"IntentBridgeCoordinatorV2:${COORDINATOR_V2}:IntentBridgeCoordinatorV2" \
|
||||
"DodoRouteExecutorAdapter:${DODO_ADAPTER}:DodoRouteExecutorAdapter" \
|
||||
"DodoV3RouteExecutorAdapter:${DODO_V3_ADAPTER}:DodoV3RouteExecutorAdapter" \
|
||||
"UniswapV3RouteExecutorAdapter:${UNISWAP_V3_ADAPTER}:UniswapV3RouteExecutorAdapter" \
|
||||
"BalancerRouteExecutorAdapter:${BALANCER_ADAPTER}:BalancerRouteExecutorAdapter" \
|
||||
"CurveRouteExecutorAdapter:${CURVE_ADAPTER}:CurveRouteExecutorAdapter" \
|
||||
"OneInchRouteExecutorAdapter:${ONEINCH_ADAPTER}:OneInchRouteExecutorAdapter" \
|
||||
"Chain138PilotUniswapV3Router:${PILOT_UNISWAP}:Chain138PilotUniswapV3Router" \
|
||||
"Chain138PilotBalancerVault:${PILOT_BALANCER}:Chain138PilotBalancerVault" \
|
||||
"Chain138PilotCurve3Pool:${PILOT_CURVE}:Chain138PilotCurve3Pool" \
|
||||
"Chain138PilotOneInchAggregationRouter:${PILOT_ONEINCH}:Chain138PilotOneInchAggregationRouter"
|
||||
do
|
||||
IFS=":" read -r label addr expected <<<"${pair}"
|
||||
should_handle "${label}" || continue
|
||||
if is_verified "${addr}" "${expected}"; then
|
||||
ok "${label} already verified on Blockscout."
|
||||
else
|
||||
warn "${label} not yet verified on Blockscout."
|
||||
fi
|
||||
done
|
||||
exit 0
|
||||
fi
|
||||
|
||||
should_handle "EnhancedSwapRouterV2" && submit_best_verification \
|
||||
"EnhancedSwapRouterV2" \
|
||||
"${ROUTER_V2}" \
|
||||
"contracts/bridge/trustless/EnhancedSwapRouterV2.sol:EnhancedSwapRouterV2" \
|
||||
"EnhancedSwapRouterV2" \
|
||||
"constructor(address,address,address,address)" \
|
||||
"${WETH}" "${USDT}" "${USDC}" "${DAI_PLACEHOLDER}"
|
||||
|
||||
should_handle "IntentBridgeCoordinatorV2" && submit_best_verification \
|
||||
"IntentBridgeCoordinatorV2" \
|
||||
"${COORDINATOR_V2}" \
|
||||
"contracts/bridge/trustless/IntentBridgeCoordinatorV2.sol:IntentBridgeCoordinatorV2" \
|
||||
"IntentBridgeCoordinatorV2" \
|
||||
"constructor(address)" \
|
||||
"${ROUTER_V2}"
|
||||
|
||||
should_handle "DodoRouteExecutorAdapter" && submit_best_verification \
|
||||
"DodoRouteExecutorAdapter" \
|
||||
"${DODO_ADAPTER}" \
|
||||
"contracts/bridge/trustless/adapters/DodoRouteExecutorAdapter.sol:DodoRouteExecutorAdapter" \
|
||||
"DodoRouteExecutorAdapter" \
|
||||
""
|
||||
|
||||
should_handle "DodoV3RouteExecutorAdapter" && submit_best_verification \
|
||||
"DodoV3RouteExecutorAdapter" \
|
||||
"${DODO_V3_ADAPTER}" \
|
||||
"contracts/bridge/trustless/adapters/DodoV3RouteExecutorAdapter.sol:DodoV3RouteExecutorAdapter" \
|
||||
"DodoV3RouteExecutorAdapter" \
|
||||
""
|
||||
|
||||
should_handle "UniswapV3RouteExecutorAdapter" && submit_best_verification \
|
||||
"UniswapV3RouteExecutorAdapter" \
|
||||
"${UNISWAP_V3_ADAPTER}" \
|
||||
"contracts/bridge/trustless/adapters/UniswapV3RouteExecutorAdapter.sol:UniswapV3RouteExecutorAdapter" \
|
||||
"UniswapV3RouteExecutorAdapter" \
|
||||
""
|
||||
|
||||
should_handle "BalancerRouteExecutorAdapter" && submit_best_verification \
|
||||
"BalancerRouteExecutorAdapter" \
|
||||
"${BALANCER_ADAPTER}" \
|
||||
"contracts/bridge/trustless/adapters/BalancerRouteExecutorAdapter.sol:BalancerRouteExecutorAdapter" \
|
||||
"BalancerRouteExecutorAdapter" \
|
||||
""
|
||||
|
||||
should_handle "CurveRouteExecutorAdapter" && submit_best_verification \
|
||||
"CurveRouteExecutorAdapter" \
|
||||
"${CURVE_ADAPTER}" \
|
||||
"contracts/bridge/trustless/adapters/CurveRouteExecutorAdapter.sol:CurveRouteExecutorAdapter" \
|
||||
"CurveRouteExecutorAdapter" \
|
||||
""
|
||||
|
||||
should_handle "OneInchRouteExecutorAdapter" && submit_best_verification \
|
||||
"OneInchRouteExecutorAdapter" \
|
||||
"${ONEINCH_ADAPTER}" \
|
||||
"contracts/bridge/trustless/adapters/OneInchRouteExecutorAdapter.sol:OneInchRouteExecutorAdapter" \
|
||||
"OneInchRouteExecutorAdapter" \
|
||||
""
|
||||
|
||||
should_handle "Chain138PilotUniswapV3Router" && submit_best_verification \
|
||||
"Chain138PilotUniswapV3Router" \
|
||||
"${PILOT_UNISWAP}" \
|
||||
"contracts/bridge/trustless/pilot/Chain138PilotDexVenues.sol:Chain138PilotUniswapV3Router" \
|
||||
"Chain138PilotUniswapV3Router" \
|
||||
""
|
||||
|
||||
should_handle "Chain138PilotBalancerVault" && submit_best_verification \
|
||||
"Chain138PilotBalancerVault" \
|
||||
"${PILOT_BALANCER}" \
|
||||
"contracts/bridge/trustless/pilot/Chain138PilotDexVenues.sol:Chain138PilotBalancerVault" \
|
||||
"Chain138PilotBalancerVault" \
|
||||
""
|
||||
|
||||
should_handle "Chain138PilotCurve3Pool" && submit_best_verification \
|
||||
"Chain138PilotCurve3Pool" \
|
||||
"${PILOT_CURVE}" \
|
||||
"contracts/bridge/trustless/pilot/Chain138PilotDexVenues.sol:Chain138PilotCurve3Pool" \
|
||||
"Chain138PilotCurve3Pool" \
|
||||
"constructor(address,address,address,uint256)" \
|
||||
"${USDT}" "${USDC}" "0x0000000000000000000000000000000000000000" "4"
|
||||
|
||||
should_handle "Chain138PilotOneInchAggregationRouter" && submit_best_verification \
|
||||
"Chain138PilotOneInchAggregationRouter" \
|
||||
"${PILOT_ONEINCH}" \
|
||||
"contracts/bridge/trustless/pilot/Chain138PilotDexVenues.sol:Chain138PilotOneInchAggregationRouter" \
|
||||
"Chain138PilotOneInchAggregationRouter" \
|
||||
""
|
||||
|
||||
if (( NO_WAIT )); then
|
||||
log
|
||||
ok "Chain 138 route execution stack verification submissions complete."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
log
|
||||
ok "Chain 138 route execution stack verification flow complete."
|
||||
@@ -273,16 +273,9 @@ submit_v2_standard_input() {
|
||||
|
||||
compiler_version="v0.8.16+commit.07a7930e"
|
||||
license_type="busl_1_1"
|
||||
input_file="$(mktemp)"
|
||||
|
||||
jq '
|
||||
del(.settings.libraries[""].__CACHE_BREAKER__)
|
||||
| if (.settings.libraries[""]? == {}) then del(.settings.libraries[""]) else . end
|
||||
| if (.settings.libraries? == {}) then del(.settings.libraries) else . end
|
||||
' "${input}" > "${input_file}"
|
||||
|
||||
response="$(
|
||||
curl --max-time 120 -fsS -X POST \
|
||||
curl --max-time 30 -fsS -X POST \
|
||||
-F "compiler_version=${compiler_version}" \
|
||||
-F "contract_name=${path}" \
|
||||
-F "autodetect_constructor_args=false" \
|
||||
@@ -290,13 +283,9 @@ submit_v2_standard_input() {
|
||||
-F "optimization_runs=200" \
|
||||
-F "is_optimization_enabled=true" \
|
||||
-F "license_type=${license_type}" \
|
||||
-F "files[0]=@${input_file};type=application/json" \
|
||||
-F "files[0]=@${input};type=application/json" \
|
||||
"${BLOCKSCOUT_URL}/api/v2/smart-contracts/${addr}/verification/via/standard-input"
|
||||
)" || {
|
||||
rm -f "${input_file}"
|
||||
fail "${label}: Blockscout standard-input submission failed."
|
||||
}
|
||||
rm -f "${input_file}"
|
||||
)" || fail "${label}: Blockscout standard-input submission failed."
|
||||
|
||||
message="$(jq -r '.message // empty' <<<"${response}")"
|
||||
if [[ "${message}" == "Smart-contract verification started" ]]; then
|
||||
|
||||
210
token-aggregation-build/config/mainnet-aave-dex-parity.json
Normal file
210
token-aggregation-build/config/mainnet-aave-dex-parity.json
Normal file
@@ -0,0 +1,210 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"schemaVersion": 1,
|
||||
"description": "Ethereum mainnet Uniswap V3 Quoter v1 legs for Aave V3 top-supply underlyings. Implied USD = USDC out / 10^6 for amountIn of the asset. Extend `assets` as pools change.",
|
||||
"quoterV1": "0xb27308f9F90D607463bb33eA1BeBb41C27CE5AB6",
|
||||
"chainId": 1,
|
||||
"referenceStable": {
|
||||
"symbol": "USDC",
|
||||
"address": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
|
||||
"decimals": 6
|
||||
},
|
||||
"weth": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
|
||||
"feeTiersTry": [100, 500, 3000, 10000],
|
||||
"assets": [
|
||||
{
|
||||
"symbol": "weETH",
|
||||
"address": "0xCd5fE23C85820F7B72D0926FC9b05b43E359b7ee",
|
||||
"decimals": 18,
|
||||
"amountIn": "1000000000000000000",
|
||||
"quote": {
|
||||
"type": "path",
|
||||
"tokens": [
|
||||
"0xCd5fE23C85820F7B72D0926FC9b05b43E359b7ee",
|
||||
"0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
|
||||
"0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
|
||||
],
|
||||
"fees": [500, 500],
|
||||
"label": "weETH->WETH@500->USDC@500"
|
||||
}
|
||||
},
|
||||
{
|
||||
"symbol": "wstETH",
|
||||
"address": "0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0",
|
||||
"decimals": 18,
|
||||
"amountIn": "1000000000000000000",
|
||||
"quote": {
|
||||
"type": "path",
|
||||
"tokens": [
|
||||
"0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0",
|
||||
"0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
|
||||
"0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
|
||||
],
|
||||
"fees": [100, 500],
|
||||
"label": "wstETH->WETH@100->USDC@500"
|
||||
}
|
||||
},
|
||||
{
|
||||
"symbol": "WBTC",
|
||||
"address": "0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599",
|
||||
"decimals": 8,
|
||||
"amountIn": "100000000",
|
||||
"quote": {
|
||||
"type": "path",
|
||||
"tokens": [
|
||||
"0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599",
|
||||
"0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
|
||||
],
|
||||
"fees": [500],
|
||||
"label": "WBTC->USDC@500 (canonical deep pool)"
|
||||
}
|
||||
},
|
||||
{
|
||||
"symbol": "cbBTC",
|
||||
"address": "0xcbB7C0000aB88B473b1f5aFd9ef808440eed33Bf",
|
||||
"decimals": 8,
|
||||
"amountIn": "100000000",
|
||||
"quote": {
|
||||
"type": "path",
|
||||
"tokens": [
|
||||
"0xcbB7C0000aB88B473b1f5aFd9ef808440eed33Bf",
|
||||
"0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599",
|
||||
"0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
|
||||
],
|
||||
"fees": [500, 500],
|
||||
"label": "cbBTC->WBTC@500->USDC@500"
|
||||
}
|
||||
},
|
||||
{
|
||||
"symbol": "rsETH",
|
||||
"address": "0xA1290d69c65A6Fe4DF752f95823fae25cB99e5A7",
|
||||
"decimals": 18,
|
||||
"amountIn": "1000000000000000000",
|
||||
"quote": {
|
||||
"type": "compose_via_weth",
|
||||
"feeTiersToWeth": [100, 500, 3000, 10000],
|
||||
"usdcFee": 500,
|
||||
"label": "rsETH->WETH (best tier) × WETH->USDC@500"
|
||||
}
|
||||
},
|
||||
{
|
||||
"symbol": "USDT",
|
||||
"address": "0xdAC17F958D2ee523a2206206994597C13D831ec7",
|
||||
"decimals": 6,
|
||||
"amountIn": "1000000",
|
||||
"quote": {
|
||||
"type": "single_best",
|
||||
"tokenOut": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
|
||||
"label": "USDT->USDC (best of fee tiers)"
|
||||
}
|
||||
},
|
||||
{
|
||||
"symbol": "sUSDe",
|
||||
"address": "0x9D39A5DE30e57443BfF2A8307A4256c8797A3497",
|
||||
"decimals": 18,
|
||||
"amountIn": "1000000000000000000",
|
||||
"quote": {
|
||||
"type": "single_best",
|
||||
"tokenOut": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
|
||||
"label": "sUSDe->USDC (best of fee tiers)"
|
||||
}
|
||||
},
|
||||
{
|
||||
"symbol": "WETH",
|
||||
"address": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
|
||||
"decimals": 18,
|
||||
"amountIn": "1000000000000000000",
|
||||
"quote": {
|
||||
"type": "single_best",
|
||||
"tokenOut": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
|
||||
"label": "WETH->USDC (best of fee tiers)"
|
||||
}
|
||||
},
|
||||
{
|
||||
"symbol": "USDC",
|
||||
"address": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
|
||||
"decimals": 6,
|
||||
"amountIn": "1000000",
|
||||
"quote": {
|
||||
"type": "identity_usd",
|
||||
"label": "1 USDC = 1 USD reference"
|
||||
}
|
||||
},
|
||||
{
|
||||
"symbol": "USDe",
|
||||
"address": "0x4c9EDD5852cd905f086C759E8383e09bff1E68B3",
|
||||
"decimals": 18,
|
||||
"amountIn": "1000000000000000000",
|
||||
"quote": {
|
||||
"type": "single_best",
|
||||
"tokenOut": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
|
||||
"label": "USDe->USDC (best of fee tiers)"
|
||||
}
|
||||
},
|
||||
{
|
||||
"symbol": "PT-sUSDE-7MAY2026",
|
||||
"address": "0x3de0ff76E8b528C092d47b9DaC775931cef80F49",
|
||||
"decimals": 18,
|
||||
"amountIn": "1000000000000000000",
|
||||
"quote": {
|
||||
"type": "single_best",
|
||||
"tokenOut": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
|
||||
"label": "PT->USDC (best effort; verify Pendle NAV)"
|
||||
}
|
||||
},
|
||||
{
|
||||
"symbol": "osETH",
|
||||
"address": "0xf1C9acDc66974dFB6dEcB12aA385b9cD01190E38",
|
||||
"decimals": 18,
|
||||
"amountIn": "1000000000000000000",
|
||||
"quote": {
|
||||
"type": "path",
|
||||
"tokens": [
|
||||
"0xf1C9acDc66974dFB6dEcB12aA385b9cD01190E38",
|
||||
"0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
|
||||
"0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
|
||||
],
|
||||
"fees": [500, 500],
|
||||
"label": "osETH->WETH@500->USDC@500"
|
||||
}
|
||||
},
|
||||
{
|
||||
"symbol": "GHO",
|
||||
"address": "0x40D16FC0246aD3160Ccc09B8D0D3A2cD28aE6C2f",
|
||||
"decimals": 18,
|
||||
"amountIn": "1000000000000000000",
|
||||
"quote": {
|
||||
"type": "single_best",
|
||||
"tokenOut": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
|
||||
"label": "GHO->USDC (best of fee tiers)"
|
||||
}
|
||||
},
|
||||
{
|
||||
"symbol": "LBTC",
|
||||
"address": "0x8236a87084f8B84306f72007F36F2618A5634494",
|
||||
"decimals": 8,
|
||||
"amountIn": "100000000",
|
||||
"quote": {
|
||||
"type": "path",
|
||||
"tokens": [
|
||||
"0x8236a87084f8B84306f72007F36F2618A5634494",
|
||||
"0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599",
|
||||
"0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
|
||||
],
|
||||
"fees": [500, 500],
|
||||
"label": "LBTC->WBTC@500->USDC@500"
|
||||
}
|
||||
},
|
||||
{
|
||||
"symbol": "RLUSD",
|
||||
"address": "0x8292Bb45bf1Ee4d140127049757C2E0fF06317eD",
|
||||
"decimals": 18,
|
||||
"amountIn": "1000000000000000000",
|
||||
"quote": {
|
||||
"type": "single_best",
|
||||
"tokenOut": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
|
||||
"label": "RLUSD->USDC (best of fee tiers)"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -230,14 +230,29 @@ Configure DEX factory addresses in `src/config/dex-factories.ts` or via environm
|
||||
```bash
|
||||
# ChainID 138
|
||||
CHAIN_138_DODO_POOL_MANAGER=0x...
|
||||
CHAIN_138_UNISWAP_V2_FACTORY=0x...
|
||||
CHAIN_138_UNISWAP_V3_FACTORY=0x...
|
||||
CHAIN_138_UNISWAP_V2_FACTORY=0x0C30F6e67Ab3667fCc2f5CEA8e274ef1FB920279
|
||||
CHAIN_138_UNISWAP_V2_ROUTER=0x3019A7fDc76ba7F64F18d78e66842760037ee638
|
||||
CHAIN_138_UNISWAP_V2_START_BLOCK=4041370
|
||||
CHAIN_138_SUSHISWAP_FACTORY=0x2871207ff0d56089D70c0134d33f1291B6Fce0BE
|
||||
CHAIN_138_SUSHISWAP_ROUTER=0xB37b93D38559f53b62ab020A14919f2630a1aE34
|
||||
CHAIN_138_SUSHISWAP_START_BLOCK=4041495
|
||||
CHAIN_138_UNISWAP_V3_FACTORY=0x2f7219276e3ce367dB9ec74C1196a8ecEe67841C
|
||||
CHAIN_138_UNISWAP_V3_ROUTER=0xde9cD8ee2811E6E64a41D5F68Be315d33995975E
|
||||
|
||||
# ChainID 651940
|
||||
CHAIN_651940_UNISWAP_V2_FACTORY=0x...
|
||||
CHAIN_651940_UNISWAP_V2_ROUTER=0x...
|
||||
CHAIN_651940_UNISWAP_V2_START_BLOCK=0
|
||||
CHAIN_651940_UNISWAP_V3_FACTORY=0x...
|
||||
CHAIN_651940_UNISWAP_V3_ROUTER=0x...
|
||||
CHAIN_651940_UNISWAP_V3_START_BLOCK=0
|
||||
CHAIN_651940_HYDX_FACTORY=0x...
|
||||
CHAIN_651940_HYDX_ROUTER=0x...
|
||||
CHAIN_651940_HYDX_START_BLOCK=0
|
||||
```
|
||||
|
||||
For ALL Mainnet non-DODO discovery, the repo now treats `HYDX` as the canonical custom venue surface when factory/router details are known. The broader `651940` non-DODO inventory is tracked in `config/allmainnet-non-dodo-protocol-surface.json`.
|
||||
|
||||
## Monitoring
|
||||
|
||||
The service includes:
|
||||
|
||||
@@ -175,15 +175,30 @@ For ChainID 138, configure DODO PoolManager address:
|
||||
|
||||
```bash
|
||||
CHAIN_138_DODO_POOL_MANAGER=0x...
|
||||
CHAIN_138_UNISWAP_V2_FACTORY=0x0C30F6e67Ab3667fCc2f5CEA8e274ef1FB920279
|
||||
CHAIN_138_UNISWAP_V2_ROUTER=0x3019A7fDc76ba7F64F18d78e66842760037ee638
|
||||
CHAIN_138_UNISWAP_V2_START_BLOCK=4041370
|
||||
CHAIN_138_SUSHISWAP_FACTORY=0x2871207ff0d56089D70c0134d33f1291B6Fce0BE
|
||||
CHAIN_138_SUSHISWAP_ROUTER=0xB37b93D38559f53b62ab020A14919f2630a1aE34
|
||||
CHAIN_138_SUSHISWAP_START_BLOCK=4041495
|
||||
```
|
||||
|
||||
For ChainID 651940, configure DEX factories as they are discovered:
|
||||
|
||||
```bash
|
||||
CHAIN_651940_UNISWAP_V2_FACTORY=0x...
|
||||
CHAIN_651940_UNISWAP_V2_ROUTER=0x...
|
||||
CHAIN_651940_UNISWAP_V2_START_BLOCK=0
|
||||
CHAIN_651940_UNISWAP_V3_FACTORY=0x...
|
||||
CHAIN_651940_UNISWAP_V3_ROUTER=0x...
|
||||
CHAIN_651940_UNISWAP_V3_START_BLOCK=0
|
||||
CHAIN_651940_HYDX_FACTORY=0x...
|
||||
CHAIN_651940_HYDX_ROUTER=0x...
|
||||
CHAIN_651940_HYDX_START_BLOCK=0
|
||||
```
|
||||
|
||||
The canonical ALL Mainnet non-DODO inventory is also tracked in the parent repo at `config/allmainnet-non-dodo-protocol-surface.json`.
|
||||
|
||||
## Monitoring
|
||||
|
||||
### Health Checks
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
import { createServer } from 'http';
|
||||
import express from 'express';
|
||||
import reportRoutes from './report';
|
||||
import { getCanonicalTokenBySymbol } from '../../config/canonical-tokens';
|
||||
|
||||
jest.mock('../../database/repositories/token-repo', () => ({
|
||||
TokenRepository: jest.fn().mockImplementation(() => ({
|
||||
@@ -124,6 +125,30 @@ describe('Report API', () => {
|
||||
])
|
||||
);
|
||||
});
|
||||
|
||||
it('fills canonical fallback usd pricing when market data is absent', async () => {
|
||||
const weth = getCanonicalTokenBySymbol(138, 'WETH');
|
||||
expect(weth?.addresses[138]).toBeTruthy();
|
||||
const wethAddress = String(weth?.addresses[138]).toLowerCase();
|
||||
|
||||
const res = await fetch(`${baseUrl}/api/v1/report/all?chainId=138`);
|
||||
expect(res.status).toBe(200);
|
||||
const body = (await res.json()) as Record<string, any>;
|
||||
const tokens138 = body.tokens?.['138'];
|
||||
expect(Array.isArray(tokens138)).toBe(true);
|
||||
|
||||
const wethEntry = tokens138.find((token: Record<string, any>) => token.address === wethAddress);
|
||||
expect(wethEntry).toMatchObject({
|
||||
symbol: 'WETH',
|
||||
decimals: 18,
|
||||
market: expect.objectContaining({
|
||||
priceUsd: 2490,
|
||||
volume24h: 0,
|
||||
liquidityUsd: 0,
|
||||
lastUpdated: '2026-04-15T00:00:00.000Z',
|
||||
}),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('GET /api/v1/report/gas-registry', () => {
|
||||
|
||||
@@ -26,6 +26,7 @@ import {
|
||||
loadDeploymentStatusFile,
|
||||
type CwRegistryChain,
|
||||
} from '../../config/deployment-status';
|
||||
import { getCanonicalPriceSnapshotGeneratedAt, getCanonicalPriceUsd } from '../../services/canonical-price-oracle';
|
||||
|
||||
const router: Router = Router();
|
||||
const tokenRepo = new TokenRepository();
|
||||
@@ -94,6 +95,8 @@ async function buildTokenReport(chainId: number) {
|
||||
})
|
||||
);
|
||||
|
||||
const fallbackPriceUsd = getCanonicalPriceUsd(chainId, address);
|
||||
|
||||
out.push({
|
||||
chainId,
|
||||
address: address.toLowerCase(),
|
||||
@@ -110,7 +113,7 @@ async function buildTokenReport(chainId: number) {
|
||||
liquiditySourceSymbol: spec.liquiditySourceSymbol,
|
||||
market: marketData
|
||||
? {
|
||||
priceUsd: marketData.priceUsd,
|
||||
priceUsd: marketData.priceUsd ?? fallbackPriceUsd,
|
||||
volume24h: marketData.volume24h,
|
||||
volume7d: marketData.volume7d,
|
||||
volume30d: marketData.volume30d,
|
||||
@@ -118,6 +121,15 @@ async function buildTokenReport(chainId: number) {
|
||||
liquidityUsd: marketData.liquidityUsd,
|
||||
lastUpdated: marketData.lastUpdated?.toISOString() ?? '',
|
||||
}
|
||||
: fallbackPriceUsd !== undefined
|
||||
? {
|
||||
priceUsd: fallbackPriceUsd,
|
||||
volume24h: 0,
|
||||
volume7d: 0,
|
||||
volume30d: 0,
|
||||
liquidityUsd: 0,
|
||||
lastUpdated: `${getCanonicalPriceSnapshotGeneratedAt()}T00:00:00.000Z`,
|
||||
}
|
||||
: undefined,
|
||||
pools: resolvedPools.map((p) => ({
|
||||
poolAddress: p.poolAddress,
|
||||
|
||||
@@ -0,0 +1,213 @@
|
||||
import { createServer } from 'http';
|
||||
import express from 'express';
|
||||
import { getCanonicalTokenBySymbol } from '../../config/canonical-tokens';
|
||||
|
||||
const mockGetTokens = jest.fn();
|
||||
const mockGetToken = jest.fn();
|
||||
const mockSearchTokens = jest.fn();
|
||||
const mockGetMarketData = jest.fn();
|
||||
const mockGetPoolsByToken = jest.fn();
|
||||
const mockGetPool = jest.fn();
|
||||
const mockGetLiveDodoPools = jest.fn();
|
||||
const mockResolveTokenDisplay = jest.fn();
|
||||
const mockResolvePoolTokenDisplays = jest.fn();
|
||||
const mockGetTokenByContract = jest.fn();
|
||||
|
||||
jest.mock('../../database/repositories/token-repo', () => ({
|
||||
TokenRepository: jest.fn().mockImplementation(() => ({
|
||||
getTokens: mockGetTokens,
|
||||
getToken: mockGetToken,
|
||||
searchTokens: mockSearchTokens,
|
||||
})),
|
||||
}));
|
||||
|
||||
jest.mock('../../database/repositories/market-data-repo', () => ({
|
||||
MarketDataRepository: jest.fn().mockImplementation(() => ({
|
||||
getMarketData: mockGetMarketData,
|
||||
})),
|
||||
}));
|
||||
|
||||
jest.mock('../../database/repositories/pool-repo', () => ({
|
||||
PoolRepository: jest.fn().mockImplementation(() => ({
|
||||
getPoolsByToken: mockGetPoolsByToken,
|
||||
getPool: mockGetPool,
|
||||
})),
|
||||
}));
|
||||
|
||||
jest.mock('../../indexer/ohlcv-generator', () => ({
|
||||
OHLCVGenerator: jest.fn().mockImplementation(() => ({
|
||||
getOHLCV: jest.fn().mockResolvedValue([]),
|
||||
})),
|
||||
}));
|
||||
|
||||
jest.mock('../../adapters/coingecko-adapter', () => ({
|
||||
CoinGeckoAdapter: jest.fn().mockImplementation(() => ({
|
||||
getTokenByContract: mockGetTokenByContract,
|
||||
getTrending: jest.fn().mockResolvedValue([]),
|
||||
})),
|
||||
}));
|
||||
|
||||
jest.mock('../../adapters/cmc-adapter', () => ({
|
||||
CoinMarketCapAdapter: jest.fn().mockImplementation(() => ({
|
||||
getTokenByContract: mockGetTokenByContract,
|
||||
})),
|
||||
}));
|
||||
|
||||
jest.mock('../../adapters/dexscreener-adapter', () => ({
|
||||
DexScreenerAdapter: jest.fn().mockImplementation(() => ({
|
||||
getTokenByContract: mockGetTokenByContract,
|
||||
})),
|
||||
}));
|
||||
|
||||
jest.mock('../../services/live-dodo-fallback', () => ({
|
||||
getLiveDodoPools: (...args: unknown[]) => mockGetLiveDodoPools(...args),
|
||||
}));
|
||||
|
||||
jest.mock('../../services/token-display', () => ({
|
||||
resolveTokenDisplay: (...args: unknown[]) => mockResolveTokenDisplay(...args),
|
||||
resolvePoolTokenDisplays: (...args: unknown[]) => mockResolvePoolTokenDisplays(...args),
|
||||
}));
|
||||
|
||||
jest.mock('../middleware/cache');
|
||||
|
||||
const tokensRoutes = require('./tokens').default as typeof import('./tokens').default;
|
||||
|
||||
function createApp() {
|
||||
const app = express();
|
||||
app.use('/api/v1', tokensRoutes);
|
||||
return app;
|
||||
}
|
||||
|
||||
async function startServer(app: express.Application): Promise<{ server: ReturnType<typeof createServer>; baseUrl: string }> {
|
||||
const server = createServer(app);
|
||||
await new Promise<void>((resolve) => server.listen(0, () => resolve()));
|
||||
const port = (server.address() as { port: number }).port;
|
||||
return { server, baseUrl: `http://127.0.0.1:${port}` };
|
||||
}
|
||||
|
||||
describe('Tokens API', () => {
|
||||
let server: ReturnType<typeof createServer>;
|
||||
let baseUrl: string;
|
||||
|
||||
beforeAll(async () => {
|
||||
const app = createApp();
|
||||
const started = await startServer(app);
|
||||
server = started.server;
|
||||
baseUrl = started.baseUrl;
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
mockGetTokens.mockResolvedValue([]);
|
||||
mockGetToken.mockResolvedValue(null);
|
||||
mockSearchTokens.mockResolvedValue([]);
|
||||
mockGetMarketData.mockResolvedValue(null);
|
||||
mockGetPoolsByToken.mockResolvedValue([]);
|
||||
mockGetPool.mockResolvedValue(null);
|
||||
mockGetLiveDodoPools.mockResolvedValue([]);
|
||||
mockResolveTokenDisplay.mockResolvedValue({
|
||||
address: '',
|
||||
name: 'Unknown Token',
|
||||
symbol: 'UNKNOWN',
|
||||
decimals: 18,
|
||||
source: 'fallback',
|
||||
});
|
||||
mockResolvePoolTokenDisplays.mockResolvedValue({
|
||||
token0: { address: '', symbol: 'UNKNOWN', name: 'Unknown Token', source: 'fallback' },
|
||||
token1: { address: '', symbol: 'UNKNOWN', name: 'Unknown Token', source: 'fallback' },
|
||||
});
|
||||
mockGetTokenByContract.mockResolvedValue(null);
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
server.close((error) => {
|
||||
if (error) {
|
||||
reject(error);
|
||||
return;
|
||||
}
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('lists canonical 138 tokens with stable and ETH-family fallback pricing when db market data is missing', async () => {
|
||||
const usdt = getCanonicalTokenBySymbol(138, 'USDT');
|
||||
const weth = getCanonicalTokenBySymbol(138, 'WETH');
|
||||
const weth10 = getCanonicalTokenBySymbol(138, 'WETH10');
|
||||
|
||||
expect(usdt?.addresses[138]).toBeTruthy();
|
||||
expect(weth?.addresses[138]).toBeTruthy();
|
||||
expect(weth10?.addresses[138]).toBeTruthy();
|
||||
|
||||
const res = await fetch(`${baseUrl}/api/v1/tokens?chainId=138&limit=400`);
|
||||
expect(res.status).toBe(200);
|
||||
|
||||
const body = (await res.json()) as Record<string, any>;
|
||||
expect(body.source).toBe('canonical');
|
||||
|
||||
const findByAddress = (address?: string) =>
|
||||
body.tokens.find((token: Record<string, any>) => token.address === address?.toLowerCase());
|
||||
|
||||
expect(findByAddress(usdt?.addresses[138])).toMatchObject({
|
||||
symbol: 'USDT',
|
||||
decimals: 6,
|
||||
market: expect.objectContaining({
|
||||
priceUsd: 1,
|
||||
volume24h: 0,
|
||||
liquidityUsd: 0,
|
||||
}),
|
||||
});
|
||||
|
||||
expect(findByAddress(weth?.addresses[138])).toMatchObject({
|
||||
symbol: 'WETH',
|
||||
decimals: 18,
|
||||
market: expect.objectContaining({
|
||||
priceUsd: 2490,
|
||||
}),
|
||||
});
|
||||
|
||||
expect(findByAddress(weth10?.addresses[138])).toMatchObject({
|
||||
symbol: 'WETH10',
|
||||
decimals: 18,
|
||||
market: expect.objectContaining({
|
||||
priceUsd: 2490,
|
||||
}),
|
||||
});
|
||||
});
|
||||
|
||||
it('fills missing priceUsd on token detail responses while preserving repository market fields', async () => {
|
||||
const weth10 = getCanonicalTokenBySymbol(138, 'WETH10');
|
||||
expect(weth10?.addresses[138]).toBeTruthy();
|
||||
const weth10Address = String(weth10?.addresses[138]).toLowerCase();
|
||||
|
||||
mockGetMarketData.mockResolvedValue({
|
||||
chainId: 138,
|
||||
tokenAddress: weth10Address,
|
||||
priceUsd: undefined,
|
||||
volume24h: 1234,
|
||||
volume7d: 5678,
|
||||
volume30d: 9012,
|
||||
liquidityUsd: 3456,
|
||||
holdersCount: 78,
|
||||
transfers24h: 9,
|
||||
lastUpdated: new Date('2026-04-16T00:00:00.000Z'),
|
||||
});
|
||||
|
||||
const res = await fetch(`${baseUrl}/api/v1/tokens/${weth10Address}?chainId=138`);
|
||||
expect(res.status).toBe(200);
|
||||
|
||||
const body = (await res.json()) as Record<string, any>;
|
||||
expect(body.token).toMatchObject({
|
||||
symbol: 'WETH10',
|
||||
decimals: 18,
|
||||
market: expect.objectContaining({
|
||||
priceUsd: 2490,
|
||||
volume24h: 1234,
|
||||
liquidityUsd: 3456,
|
||||
}),
|
||||
hasDodoPool: false,
|
||||
});
|
||||
expect(body.token.canonicalLiquidity).toBeUndefined();
|
||||
});
|
||||
});
|
||||
@@ -16,6 +16,7 @@ import {
|
||||
resolveCanonicalQuoteAddress,
|
||||
} from '../../config/canonical-tokens';
|
||||
import { getLiveDodoPools } from '../../services/live-dodo-fallback';
|
||||
import { getCanonicalPriceSnapshotGeneratedAt, getCanonicalPriceUsd } from '../../services/canonical-price-oracle';
|
||||
|
||||
const router: Router = Router();
|
||||
const tokenRepo = new TokenRepository();
|
||||
@@ -26,6 +27,40 @@ const coingeckoAdapter = new CoinGeckoAdapter();
|
||||
const cmcAdapter = new CoinMarketCapAdapter();
|
||||
const dexscreenerAdapter = new DexScreenerAdapter();
|
||||
|
||||
function withCanonicalMarketFallback(
|
||||
chainId: number,
|
||||
address: string,
|
||||
marketData: Awaited<ReturnType<MarketDataRepository['getMarketData']>>
|
||||
) {
|
||||
const fallbackPriceUsd = getCanonicalPriceUsd(chainId, address);
|
||||
if (marketData) {
|
||||
if (marketData.priceUsd !== undefined || fallbackPriceUsd === undefined) {
|
||||
return marketData;
|
||||
}
|
||||
return {
|
||||
...marketData,
|
||||
priceUsd: fallbackPriceUsd,
|
||||
};
|
||||
}
|
||||
|
||||
if (fallbackPriceUsd === undefined) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
chainId,
|
||||
tokenAddress: address.toLowerCase(),
|
||||
priceUsd: fallbackPriceUsd,
|
||||
volume24h: 0,
|
||||
volume7d: 0,
|
||||
volume30d: 0,
|
||||
liquidityUsd: 0,
|
||||
holdersCount: 0,
|
||||
transfers24h: 0,
|
||||
lastUpdated: new Date(`${getCanonicalPriceSnapshotGeneratedAt()}T00:00:00Z`),
|
||||
};
|
||||
}
|
||||
|
||||
function tokenFromCanonical(chainId: number, address: string): Token | null {
|
||||
const spec = getCanonicalTokenByAddress(chainId, address.toLowerCase());
|
||||
if (!spec) {
|
||||
@@ -45,10 +80,15 @@ function tokenFromCanonical(chainId: number, address: string): Token | null {
|
||||
async function getPoolsByTokenWithFallback(chainId: number, address: string): Promise<LiquidityPool[]> {
|
||||
const normalized = address.toLowerCase();
|
||||
const resolution = resolveCanonicalQuoteAddress(chainId, normalized);
|
||||
const dbPools = filterPoolsForExposure(
|
||||
chainId,
|
||||
await poolRepo.getPoolsByToken(chainId, resolution.lookupAddress)
|
||||
);
|
||||
let dbPools: LiquidityPool[] = [];
|
||||
try {
|
||||
dbPools = filterPoolsForExposure(
|
||||
chainId,
|
||||
await poolRepo.getPoolsByToken(chainId, resolution.lookupAddress)
|
||||
);
|
||||
} catch (error) {
|
||||
logger.warn('DB pool lookup failed; using live DODO fallback', { chainId, address: resolution.lookupAddress, error });
|
||||
}
|
||||
if (dbPools.length > 0) {
|
||||
return dbPools;
|
||||
}
|
||||
@@ -178,9 +218,10 @@ router.get('/tokens', cacheMiddleware(60 * 1000), async (req: Request, res: Resp
|
||||
const tokensWithMarketData = await Promise.all(
|
||||
tokens.map(async (token) => {
|
||||
const marketData = await marketDataRepo.getMarketData(chainId, token.address);
|
||||
const market = withCanonicalMarketFallback(chainId, token.address, marketData);
|
||||
const out: Record<string, unknown> = {
|
||||
...token,
|
||||
market: marketData || undefined,
|
||||
market: market || undefined,
|
||||
};
|
||||
if (includeDodoPool) {
|
||||
const pools = await getPoolsByTokenWithFallback(chainId, token.address);
|
||||
@@ -223,13 +264,14 @@ router.get('/tokens/:address', cacheMiddleware(60 * 1000), async (req: Request,
|
||||
return res.status(404).json({ error: 'Token not found' });
|
||||
}
|
||||
|
||||
const [marketData, pools, coingeckoData, cmcData, dexscreenerData] = await Promise.all([
|
||||
const [marketDataRaw, pools, coingeckoData, cmcData, dexscreenerData] = await Promise.all([
|
||||
marketDataRepo.getMarketData(chainId, resolution.lookupAddress),
|
||||
getPoolsByTokenWithFallback(chainId, normalizedAddress),
|
||||
coingeckoAdapter.getTokenByContract(chainId, resolution.lookupAddress),
|
||||
cmcAdapter.getTokenByContract(chainId, resolution.lookupAddress),
|
||||
dexscreenerAdapter.getTokenByContract(chainId, resolution.lookupAddress),
|
||||
]);
|
||||
const marketData = withCanonicalMarketFallback(chainId, normalizedAddress, marketDataRaw);
|
||||
|
||||
res.json({
|
||||
token: {
|
||||
|
||||
@@ -115,6 +115,26 @@ describe('canonical cW token catalog', () => {
|
||||
expect(cwethL2?.addresses[10]).toBe('0xce7200000000000000000000000000000000000a');
|
||||
expect(getCanonicalTokenByAddress(10, '0xce7200000000000000000000000000000000000a')?.symbol).toBe('cWETHL2');
|
||||
expect(getTokenRegistryFamily(cwethL2!)).toBe('gas_native');
|
||||
|
||||
const weth = getCanonicalTokenBySymbol(138, 'WETH');
|
||||
expect(weth).toMatchObject({
|
||||
symbol: 'WETH',
|
||||
type: 'w',
|
||||
currencyCode: 'ETH',
|
||||
decimals: 18,
|
||||
});
|
||||
expect(weth?.addresses[138]).toBe('0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2');
|
||||
expect(getCanonicalTokenByAddress(138, '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2')?.symbol).toBe('WETH');
|
||||
|
||||
const weth10 = getCanonicalTokenBySymbol(138, 'WETH10');
|
||||
expect(weth10).toMatchObject({
|
||||
symbol: 'WETH10',
|
||||
type: 'w',
|
||||
currencyCode: 'ETH',
|
||||
decimals: 18,
|
||||
});
|
||||
expect(weth10?.addresses[138]).toBe('0xf4BB2e28688e89fCcE3c0580D37d36A7672E8A9f');
|
||||
expect(getCanonicalTokenByAddress(138, '0xf4BB2e28688e89fCcE3c0580D37d36A7672E8A9f')?.symbol).toBe('WETH10');
|
||||
});
|
||||
|
||||
it('surfaces cAUSDT on Chain 138 from env and keeps cWAUSDT fallback mirrors on active public chains', () => {
|
||||
|
||||
@@ -128,6 +128,10 @@ const FALLBACK_ADDRESSES: Record<string, Partial<Record<number, string>>> = {
|
||||
cUSDW: {
|
||||
[CHAIN_138]: '0xcA6BFa614935f1AB71c9aB106bAA6FBB6057095e',
|
||||
},
|
||||
// ALL / AUSDT corridor hub on Chain 138 (see config/smart-contracts-master.json COMPLIANT_AUSDT_ADDRESS)
|
||||
cAUSDT: {
|
||||
[CHAIN_138]: '0x5fdDF65733e3d590463F68f93Cf16E8c04081271',
|
||||
},
|
||||
cBTC: {
|
||||
[CHAIN_138]: '0xcb7c000000000000000000000000000000000138',
|
||||
},
|
||||
@@ -243,6 +247,8 @@ const FALLBACK_ADDRESSES: Record<string, Partial<Record<number, string>>> = {
|
||||
cCADC: { [CHAIN_138]: '0x54dBd40cF05e15906A2C21f600937e96787f5679' },
|
||||
cXAUC: { [CHAIN_138]: '0x290E52a8819A4fbD0714E517225429aA2B70EC6b' },
|
||||
cXAUT: { [CHAIN_138]: '0x94e408E26c6FD8F4ee00b54dF19082FDA07dC96E' },
|
||||
WETH: { [CHAIN_138]: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2' },
|
||||
WETH10: { [CHAIN_138]: '0xf4BB2e28688e89fCcE3c0580D37d36A7672E8A9f' },
|
||||
// ISO-4217W on Cronos (25) — from DeployISO4217WSystem
|
||||
USDW: { [CHAIN_25]: '0x948690147D2e50ffe50C5d38C14125aD6a9FA036' },
|
||||
EURW: { [CHAIN_25]: '0x58a8D8F78F1B65c06dAd7542eC46b299629A60dd' },
|
||||
@@ -446,6 +452,26 @@ export const CANONICAL_TOKENS: CanonicalTokenSpec[] = [
|
||||
{ symbol: 'cWUSDC', name: 'USD Coin (Compliant Wrapped ISO-4217 M1)', type: 'w', decimals: 6, currencyCode: 'USD', description: 'Public-network mirrored transport form of canonical Chain 138 cUSDC.', addresses: { ...Object.fromEntries(GRU_CW_CHAIN_IDS.map((id) => [id, addr('cWUSDC', id)])) } },
|
||||
{ symbol: 'cWUSDT', name: 'Tether USD (Compliant Wrapped ISO-4217 M1)', type: 'w', decimals: 6, currencyCode: 'USD', description: 'Public-network mirrored transport form of canonical Chain 138 cUSDT.', addresses: { ...Object.fromEntries(GRU_CW_CHAIN_IDS.map((id) => [id, addr('cWUSDT', id)])) } },
|
||||
{ symbol: 'cWUSDW', name: 'USD W (Compliant Wrapped ISO-4217 M1)', type: 'w', decimals: 6, currencyCode: 'USD', description: 'Public-network mirrored transport form of canonical Chain 138 cUSDW.', addresses: { ...Object.fromEntries(GRU_CW_CHAIN_IDS.map((id) => [id, addr('cWUSDW', id)])) } },
|
||||
{
|
||||
symbol: 'WETH',
|
||||
name: 'Wrapped Ether (WETH9)',
|
||||
type: 'w',
|
||||
decimals: 18,
|
||||
currencyCode: 'ETH',
|
||||
registryFamily: 'gas_native',
|
||||
description: 'Legacy WETH9 surface used on Chain 138 for canonical ETH swap routing and CCIP WETH9 bridge lanes.',
|
||||
addresses: { [CHAIN_138]: addr('WETH', CHAIN_138) || '' },
|
||||
},
|
||||
{
|
||||
symbol: 'WETH10',
|
||||
name: 'Wrapped Ether 10',
|
||||
type: 'w',
|
||||
decimals: 18,
|
||||
currencyCode: 'ETH',
|
||||
registryFamily: 'gas_native',
|
||||
description: 'Chain 138 WETH10 pilot wrapped ETH surface used by DODO v3 routing and flash-capable paths.',
|
||||
addresses: { [CHAIN_138]: addr('WETH10', CHAIN_138) || '' },
|
||||
},
|
||||
{
|
||||
symbol: 'cWBTC',
|
||||
name: 'Bitcoin (Compliant Wrapped Monetary Unit)',
|
||||
@@ -724,6 +750,8 @@ const LOGO_BY_SYMBOL: Record<string, string> = {
|
||||
cWUSDC: USDC_LOGO,
|
||||
cWUSDT: USDT_LOGO,
|
||||
cWUSDW: USDC_LOGO,
|
||||
WETH: ETH_LOGO,
|
||||
WETH10: ETH_LOGO,
|
||||
cEURC: `${GRU_LOGO_BASE}/cEURC.svg`,
|
||||
cEURT: `${GRU_LOGO_BASE}/cEURT.svg`,
|
||||
cGBPC: `${GRU_LOGO_BASE}/cGBPC.svg`,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
export type DexType = 'uniswap_v2' | 'uniswap_v3' | 'dodo' | 'custom';
|
||||
export type DexType = 'uniswap_v2' | 'uniswap_v3' | 'sushiswap' | 'dodo' | 'custom';
|
||||
|
||||
export interface UniswapV2Config {
|
||||
factory: string;
|
||||
@@ -30,6 +30,7 @@ export interface CustomDexConfig {
|
||||
export interface DexFactoryConfig {
|
||||
uniswap_v2?: UniswapV2Config[];
|
||||
uniswap_v3?: UniswapV3Config[];
|
||||
sushiswap?: UniswapV2Config[];
|
||||
dodo?: DodoConfig[];
|
||||
custom?: CustomDexConfig[];
|
||||
}
|
||||
@@ -60,6 +61,15 @@ export const DEX_FACTORIES: Record<number, DexFactoryConfig> = {
|
||||
},
|
||||
]
|
||||
: undefined,
|
||||
sushiswap: process.env.CHAIN_138_SUSHISWAP_FACTORY
|
||||
? [
|
||||
{
|
||||
factory: process.env.CHAIN_138_SUSHISWAP_FACTORY,
|
||||
router: process.env.CHAIN_138_SUSHISWAP_ROUTER || '',
|
||||
startBlock: parseInt(process.env.CHAIN_138_SUSHISWAP_START_BLOCK || '0', 10),
|
||||
},
|
||||
]
|
||||
: undefined,
|
||||
// UniswapV3 - if deployed
|
||||
uniswap_v3: process.env.CHAIN_138_UNISWAP_V3_FACTORY
|
||||
? [
|
||||
@@ -101,6 +111,16 @@ export const DEX_FACTORIES: Record<number, DexFactoryConfig> = {
|
||||
},
|
||||
]
|
||||
: undefined,
|
||||
custom: process.env.CHAIN_651940_HYDX_FACTORY
|
||||
? [
|
||||
{
|
||||
factory: process.env.CHAIN_651940_HYDX_FACTORY,
|
||||
router: process.env.CHAIN_651940_HYDX_ROUTER || '',
|
||||
startBlock: parseInt(process.env.CHAIN_651940_HYDX_START_BLOCK || '0', 10),
|
||||
pairCreatedEvent: process.env.CHAIN_651940_HYDX_PAIR_CREATED_EVENT || '',
|
||||
},
|
||||
]
|
||||
: undefined,
|
||||
},
|
||||
// cW* edge chains (1, 10, 56, 100, 137): set CHAIN_*_DODO_PMM_INTEGRATION or CHAIN_*_DODO_POOL_MANAGER to index DODO/pools
|
||||
1: {
|
||||
@@ -189,6 +209,8 @@ export function hasDexType(chainId: number, dexType: DexType): boolean {
|
||||
return !!config.uniswap_v2 && config.uniswap_v2.length > 0;
|
||||
case 'uniswap_v3':
|
||||
return !!config.uniswap_v3 && config.uniswap_v3.length > 0;
|
||||
case 'sushiswap':
|
||||
return !!config.sushiswap && config.sushiswap.length > 0;
|
||||
case 'dodo':
|
||||
return !!config.dodo && config.dodo.length > 0;
|
||||
case 'custom':
|
||||
@@ -208,6 +230,7 @@ export function getConfiguredDexTypes(chainId: number): DexType[] {
|
||||
const types: DexType[] = [];
|
||||
if (hasDexType(chainId, 'uniswap_v2')) types.push('uniswap_v2');
|
||||
if (hasDexType(chainId, 'uniswap_v3')) types.push('uniswap_v3');
|
||||
if (hasDexType(chainId, 'sushiswap')) types.push('sushiswap');
|
||||
if (hasDexType(chainId, 'dodo')) types.push('dodo');
|
||||
if (hasDexType(chainId, 'custom')) types.push('custom');
|
||||
|
||||
|
||||
@@ -94,6 +94,10 @@ function encodeOneInchRoute(router: string): string {
|
||||
return abiCoder.encode(['address', 'address', 'bytes'], [router, router, '0x']);
|
||||
}
|
||||
|
||||
function encodeRouterV2Route(factory: string, router: string): string {
|
||||
return abiCoder.encode(['address', 'address'], [factory, router]);
|
||||
}
|
||||
|
||||
function chain138DodoCapabilities(): ProviderCapabilityRecord {
|
||||
const assets = getChain138RoutingAssets();
|
||||
const dodoProvider =
|
||||
@@ -384,6 +388,140 @@ function chain138UniswapCapabilities(): ProviderCapabilityRecord {
|
||||
};
|
||||
}
|
||||
|
||||
function chain138UniswapV2Capabilities(): ProviderCapabilityRecord {
|
||||
const assets = getChain138RoutingAssets();
|
||||
const factory = normalizeAddress(process.env.CHAIN_138_UNISWAP_V2_FACTORY);
|
||||
const router = normalizeAddress(process.env.CHAIN_138_UNISWAP_V2_ROUTER);
|
||||
const wethUsdtPair = normalizeAddress(process.env.CHAIN138_UNISWAP_V2_NATIVE_WETH_USDT_PAIR);
|
||||
const wethUsdcPair = normalizeAddress(process.env.CHAIN138_UNISWAP_V2_NATIVE_WETH_USDC_PAIR);
|
||||
const cusdtCusdcPair = normalizeAddress(process.env.CHAIN138_UNISWAP_V2_NATIVE_CUSDT_CUSDC_PAIR);
|
||||
const status = factory && router ? 'live' : 'planned';
|
||||
|
||||
const pairs = [
|
||||
...bidirectionalPair({
|
||||
chainId: CHAIN_138,
|
||||
provider: 'uniswap_v2',
|
||||
tokenASymbol: 'WETH',
|
||||
tokenAAddress: assets.WETH.address,
|
||||
tokenBSymbol: 'USDT',
|
||||
tokenBAddress: assets.USDT.address,
|
||||
status,
|
||||
target: router,
|
||||
providerData: status === 'live' ? { factory, router, pair: wethUsdtPair } : undefined,
|
||||
providerDataHex: status === 'live' ? encodeRouterV2Route(factory, router) : undefined,
|
||||
notes: ['Canonical Chain 138 native Uniswap v2 WETH/USDT venue.'],
|
||||
reason: status === 'planned' ? 'Configure CHAIN_138_UNISWAP_V2_FACTORY and CHAIN_138_UNISWAP_V2_ROUTER after Chain 138 native venue deployment.' : undefined,
|
||||
}),
|
||||
...bidirectionalPair({
|
||||
chainId: CHAIN_138,
|
||||
provider: 'uniswap_v2',
|
||||
tokenASymbol: 'WETH',
|
||||
tokenAAddress: assets.WETH.address,
|
||||
tokenBSymbol: 'USDC',
|
||||
tokenBAddress: assets.USDC.address,
|
||||
status,
|
||||
target: router,
|
||||
providerData: status === 'live' ? { factory, router, pair: wethUsdcPair } : undefined,
|
||||
providerDataHex: status === 'live' ? encodeRouterV2Route(factory, router) : undefined,
|
||||
notes: ['Canonical Chain 138 native Uniswap v2 WETH/USDC venue.'],
|
||||
reason: status === 'planned' ? 'Configure CHAIN_138_UNISWAP_V2_FACTORY and CHAIN_138_UNISWAP_V2_ROUTER after Chain 138 native venue deployment.' : undefined,
|
||||
}),
|
||||
...bidirectionalPair({
|
||||
chainId: CHAIN_138,
|
||||
provider: 'uniswap_v2',
|
||||
tokenASymbol: 'cUSDT',
|
||||
tokenAAddress: assets.cUSDT.address,
|
||||
tokenBSymbol: 'cUSDC',
|
||||
tokenBAddress: assets.cUSDC.address,
|
||||
status,
|
||||
target: router,
|
||||
providerData: status === 'live' ? { factory, router, pair: cusdtCusdcPair } : undefined,
|
||||
providerDataHex: status === 'live' ? encodeRouterV2Route(factory, router) : undefined,
|
||||
notes: ['Canonical Chain 138 native Uniswap v2 GRU stable venue.'],
|
||||
reason: status === 'planned' ? 'Configure CHAIN_138_UNISWAP_V2_FACTORY and CHAIN_138_UNISWAP_V2_ROUTER after Chain 138 native venue deployment.' : undefined,
|
||||
}),
|
||||
];
|
||||
|
||||
return {
|
||||
chainId: CHAIN_138,
|
||||
provider: 'uniswap_v2',
|
||||
executionMode: 'onchain',
|
||||
live: status === 'live',
|
||||
quoteLive: status === 'live',
|
||||
executionLive: status === 'live',
|
||||
supportedLegTypes: ['swap'],
|
||||
pairs,
|
||||
notes: ['Canonical Chain 138 native Uniswap v2 router/factory path.'],
|
||||
};
|
||||
}
|
||||
|
||||
function chain138SushiswapCapabilities(): ProviderCapabilityRecord {
|
||||
const assets = getChain138RoutingAssets();
|
||||
const factory = normalizeAddress(process.env.CHAIN_138_SUSHISWAP_FACTORY);
|
||||
const router = normalizeAddress(process.env.CHAIN_138_SUSHISWAP_ROUTER);
|
||||
const wethUsdtPair = normalizeAddress(process.env.CHAIN138_SUSHISWAP_NATIVE_WETH_USDT_PAIR);
|
||||
const wethUsdcPair = normalizeAddress(process.env.CHAIN138_SUSHISWAP_NATIVE_WETH_USDC_PAIR);
|
||||
const cusdtCusdcPair = normalizeAddress(process.env.CHAIN138_SUSHISWAP_NATIVE_CUSDT_CUSDC_PAIR);
|
||||
const status = factory && router ? 'live' : 'planned';
|
||||
|
||||
const pairs = [
|
||||
...bidirectionalPair({
|
||||
chainId: CHAIN_138,
|
||||
provider: 'sushiswap',
|
||||
tokenASymbol: 'WETH',
|
||||
tokenAAddress: assets.WETH.address,
|
||||
tokenBSymbol: 'USDT',
|
||||
tokenBAddress: assets.USDT.address,
|
||||
status,
|
||||
target: router,
|
||||
providerData: status === 'live' ? { factory, router, pair: wethUsdtPair } : undefined,
|
||||
providerDataHex: status === 'live' ? encodeRouterV2Route(factory, router) : undefined,
|
||||
notes: ['Canonical Chain 138 native SushiSwap-compatible WETH/USDT venue.'],
|
||||
reason: status === 'planned' ? 'Configure CHAIN_138_SUSHISWAP_FACTORY and CHAIN_138_SUSHISWAP_ROUTER after Chain 138 Sushi deployment.' : undefined,
|
||||
}),
|
||||
...bidirectionalPair({
|
||||
chainId: CHAIN_138,
|
||||
provider: 'sushiswap',
|
||||
tokenASymbol: 'WETH',
|
||||
tokenAAddress: assets.WETH.address,
|
||||
tokenBSymbol: 'USDC',
|
||||
tokenBAddress: assets.USDC.address,
|
||||
status,
|
||||
target: router,
|
||||
providerData: status === 'live' ? { factory, router, pair: wethUsdcPair } : undefined,
|
||||
providerDataHex: status === 'live' ? encodeRouterV2Route(factory, router) : undefined,
|
||||
notes: ['Canonical Chain 138 native SushiSwap-compatible WETH/USDC venue.'],
|
||||
reason: status === 'planned' ? 'Configure CHAIN_138_SUSHISWAP_FACTORY and CHAIN_138_SUSHISWAP_ROUTER after Chain 138 Sushi deployment.' : undefined,
|
||||
}),
|
||||
...bidirectionalPair({
|
||||
chainId: CHAIN_138,
|
||||
provider: 'sushiswap',
|
||||
tokenASymbol: 'cUSDT',
|
||||
tokenAAddress: assets.cUSDT.address,
|
||||
tokenBSymbol: 'cUSDC',
|
||||
tokenBAddress: assets.cUSDC.address,
|
||||
status,
|
||||
target: router,
|
||||
providerData: status === 'live' ? { factory, router, pair: cusdtCusdcPair } : undefined,
|
||||
providerDataHex: status === 'live' ? encodeRouterV2Route(factory, router) : undefined,
|
||||
notes: ['Canonical Chain 138 native SushiSwap-compatible GRU stable venue.'],
|
||||
reason: status === 'planned' ? 'Configure CHAIN_138_SUSHISWAP_FACTORY and CHAIN_138_SUSHISWAP_ROUTER after Chain 138 Sushi deployment.' : undefined,
|
||||
}),
|
||||
];
|
||||
|
||||
return {
|
||||
chainId: CHAIN_138,
|
||||
provider: 'sushiswap',
|
||||
executionMode: 'onchain',
|
||||
live: status === 'live',
|
||||
quoteLive: status === 'live',
|
||||
executionLive: status === 'live',
|
||||
supportedLegTypes: ['swap'],
|
||||
pairs,
|
||||
notes: ['Canonical Chain 138 native SushiSwap-compatible router/factory path.'],
|
||||
};
|
||||
}
|
||||
|
||||
function chain138BalancerCapabilities(): ProviderCapabilityRecord {
|
||||
const assets = getChain138RoutingAssets();
|
||||
const vault = normalizeAddress(process.env.BALANCER_VAULT || CHAIN138_PILOT_BALANCER_VAULT);
|
||||
@@ -538,6 +676,8 @@ export function getProviderCapabilities(chainId: number): ProviderCapabilityReco
|
||||
chain138DodoCapabilities(),
|
||||
chain138DodoV3Capabilities(),
|
||||
chain138UniswapCapabilities(),
|
||||
chain138UniswapV2Capabilities(),
|
||||
chain138SushiswapCapabilities(),
|
||||
chain138BalancerCapabilities(),
|
||||
chain138CurveCapabilities(),
|
||||
chain138OneInchCapabilities(),
|
||||
|
||||
@@ -16,7 +16,7 @@ export function resolveRoutingPolicy(
|
||||
|
||||
const baseStandard: RoutingPolicy = {
|
||||
profile: 'standard',
|
||||
allowedProviders: ['dodo', 'dodo_v3', 'uniswap_v3', 'balancer', 'curve', 'one_inch'],
|
||||
allowedProviders: ['dodo', 'dodo_v3', 'uniswap_v3', 'uniswap_v2', 'sushiswap', 'balancer', 'curve', 'one_inch'],
|
||||
defaultIntermediateAddresses: defaultIntermediates,
|
||||
allowBridge: constraints.allowBridge !== false,
|
||||
allowedBridgeLabels: ['GRUTransport', 'CCIPStableBridge', 'CCIPWETH9Bridge', 'UniversalCCIPBridge', 'AlltraAdapter'],
|
||||
|
||||
@@ -41,7 +41,7 @@ export interface ApiEndpoint {
|
||||
export interface DexFactoryConfig {
|
||||
id?: number;
|
||||
chainId: number;
|
||||
dexType: 'uniswap_v2' | 'uniswap_v3' | 'dodo' | 'custom';
|
||||
dexType: 'uniswap_v2' | 'uniswap_v3' | 'sushiswap' | 'dodo' | 'custom';
|
||||
factoryAddress: string;
|
||||
routerAddress?: string;
|
||||
poolManagerAddress?: string;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Pool } from 'pg';
|
||||
import { getDatabasePool } from '../client';
|
||||
|
||||
export type DexType = 'uniswap_v2' | 'uniswap_v3' | 'dodo' | 'custom';
|
||||
export type DexType = 'uniswap_v2' | 'uniswap_v3' | 'sushiswap' | 'dodo' | 'custom';
|
||||
|
||||
export interface LiquidityPool {
|
||||
id?: number;
|
||||
|
||||
@@ -9,6 +9,7 @@ import { CoinGeckoAdapter } from '../adapters/coingecko-adapter';
|
||||
import { CoinMarketCapAdapter } from '../adapters/cmc-adapter';
|
||||
import { DexScreenerAdapter } from '../adapters/dexscreener-adapter';
|
||||
import { logger } from '../utils/logger';
|
||||
import { getCanonicalPriceUsd } from '../services/canonical-price-oracle';
|
||||
|
||||
export class ChainIndexer {
|
||||
private chainId: number;
|
||||
@@ -155,6 +156,7 @@ export class ChainIndexer {
|
||||
|
||||
// Merge external data (prefer CoinGecko, fallback to others)
|
||||
const externalData = coingeckoData || dexscreenerData || cmcData;
|
||||
const canonicalPriceUsd = getCanonicalPriceUsd(this.chainId, tokenAddress);
|
||||
|
||||
// Get pools for liquidity calculation
|
||||
const tokenPools = pools.filter(
|
||||
@@ -166,7 +168,7 @@ export class ChainIndexer {
|
||||
await this.marketDataRepo.upsertMarketData({
|
||||
chainId: this.chainId,
|
||||
tokenAddress,
|
||||
priceUsd: externalData?.priceUsd,
|
||||
priceUsd: externalData?.priceUsd ?? canonicalPriceUsd,
|
||||
priceChange24h: externalData?.priceChange24h,
|
||||
volume24h: volumeMetrics.volume24h || externalData?.volume24h || 0,
|
||||
volume7d: volumeMetrics.volume7d,
|
||||
|
||||
@@ -83,6 +83,7 @@ export class PoolIndexer {
|
||||
const hasDexConfig =
|
||||
!!dexConfig &&
|
||||
(dexConfig.uniswap_v2?.length ||
|
||||
dexConfig.sushiswap?.length ||
|
||||
dexConfig.uniswap_v3?.length ||
|
||||
dexConfig.dodo?.length ||
|
||||
dexConfig.custom?.length);
|
||||
@@ -100,7 +101,14 @@ export class PoolIndexer {
|
||||
// Index UniswapV2 pools
|
||||
if (dexConfig.uniswap_v2) {
|
||||
for (const config of dexConfig.uniswap_v2) {
|
||||
const pools = await this.indexUniswapV2Pools(config);
|
||||
const pools = await this.indexUniswapV2Pools(config, 'uniswap_v2');
|
||||
allPools.push(...pools);
|
||||
}
|
||||
}
|
||||
|
||||
if (dexConfig.sushiswap) {
|
||||
for (const config of dexConfig.sushiswap) {
|
||||
const pools = await this.indexUniswapV2Pools(config, 'sushiswap');
|
||||
allPools.push(...pools);
|
||||
}
|
||||
}
|
||||
@@ -208,7 +216,10 @@ export class PoolIndexer {
|
||||
/**
|
||||
* Index UniswapV2 pools from PairCreated events
|
||||
*/
|
||||
private async indexUniswapV2Pools(config: UniswapV2Config): Promise<LiquidityPool[]> {
|
||||
private async indexUniswapV2Pools(
|
||||
config: UniswapV2Config,
|
||||
dexType: 'uniswap_v2' | 'sushiswap'
|
||||
): Promise<LiquidityPool[]> {
|
||||
const pools: LiquidityPool[] = [];
|
||||
const factory = new ethers.Contract(config.factory, UNISWAP_V2_FACTORY_ABI, this.provider);
|
||||
|
||||
@@ -237,7 +248,7 @@ export class PoolIndexer {
|
||||
poolAddress: pairAddress.toLowerCase(),
|
||||
token0Address: token0.toLowerCase(),
|
||||
token1Address: token1.toLowerCase(),
|
||||
dexType: 'uniswap_v2',
|
||||
dexType,
|
||||
factoryAddress: config.factory.toLowerCase(),
|
||||
routerAddress: config.router?.toLowerCase(),
|
||||
reserve0: reserve0.toString(),
|
||||
@@ -255,7 +266,7 @@ export class PoolIndexer {
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error(`Error indexing UniswapV2 pools:`, error);
|
||||
logger.error(`Error indexing ${dexType} pools:`, error);
|
||||
}
|
||||
|
||||
return pools;
|
||||
@@ -390,7 +401,7 @@ export class PoolIndexer {
|
||||
}
|
||||
|
||||
try {
|
||||
if (dexType === 'uniswap_v2') {
|
||||
if (dexType === 'uniswap_v2' || dexType === 'sushiswap') {
|
||||
const pair = new ethers.Contract(poolAddress, UNISWAP_V2_PAIR_ABI, this.provider);
|
||||
const [reserve0, reserve1] = await pair.getReserves();
|
||||
|
||||
|
||||
@@ -23,6 +23,10 @@ function providerProtocol(provider: PlannerProvider): string {
|
||||
return 'dodo_v3';
|
||||
case 'uniswap_v3':
|
||||
return 'uniswap_v3';
|
||||
case 'uniswap_v2':
|
||||
return 'uniswap_v2';
|
||||
case 'sushiswap':
|
||||
return 'sushiswap';
|
||||
case 'balancer':
|
||||
return 'balancer';
|
||||
case 'curve':
|
||||
@@ -42,6 +46,10 @@ function providerLabel(provider: PlannerProvider): string {
|
||||
return 'DODO V3 / D3MM';
|
||||
case 'uniswap_v3':
|
||||
return 'Uniswap V3';
|
||||
case 'uniswap_v2':
|
||||
return 'Uniswap V2';
|
||||
case 'sushiswap':
|
||||
return 'SushiSwap';
|
||||
case 'balancer':
|
||||
return 'Balancer';
|
||||
case 'curve':
|
||||
|
||||
@@ -183,7 +183,7 @@ describe('BestExecutionPlanner', () => {
|
||||
expect(response.legs[0].provider).toBe('dodo_v3');
|
||||
expect(response.estimatedAmountOut).toBe('211660490');
|
||||
expect(response.routePlan).toBeDefined();
|
||||
expect(response.routePlan?.legs[0]?.provider).toBe(6);
|
||||
expect(response.routePlan?.legs[0]?.provider).toBe(8);
|
||||
expect(response.riskFlags).toContain('pilot-venue');
|
||||
expect(response.riskFlags).not.toContain('manual-execution-only');
|
||||
});
|
||||
|
||||
@@ -29,20 +29,24 @@ import {
|
||||
const abiCoder = AbiCoder.defaultAbiCoder();
|
||||
const ROUTER_V2_RECIPIENT_PLACEHOLDER = ZeroAddress;
|
||||
const DEFAULT_INTENT_BRIDGE_COORDINATOR_V2 = normalizeAddress('0x7D0022B7e8360172fd9C0bB6778113b7Ea3674E7');
|
||||
const PROVIDER_PRIORITY: PlannerProvider[] = ['dodo', 'dodo_v3', 'uniswap_v3', 'balancer', 'curve', 'one_inch', 'partner'];
|
||||
const PROVIDER_PRIORITY: PlannerProvider[] = ['dodo', 'dodo_v3', 'uniswap_v3', 'uniswap_v2', 'sushiswap', 'balancer', 'curve', 'one_inch', 'partner'];
|
||||
const PROVIDER_ENUM: Partial<Record<PlannerProvider, number>> = {
|
||||
dodo: 0,
|
||||
uniswap_v3: 1,
|
||||
balancer: 2,
|
||||
curve: 3,
|
||||
one_inch: 4,
|
||||
partner: 5,
|
||||
dodo_v3: 6,
|
||||
uniswap_v2: 2,
|
||||
sushiswap: 3,
|
||||
balancer: 4,
|
||||
curve: 5,
|
||||
one_inch: 6,
|
||||
partner: 7,
|
||||
dodo_v3: 8,
|
||||
};
|
||||
const PROVIDER_GAS_USD: Record<PlannerProvider, number> = {
|
||||
dodo: 0.22,
|
||||
dodo_v3: 0.3,
|
||||
uniswap_v3: 0.28,
|
||||
uniswap_v2: 0.24,
|
||||
sushiswap: 0.25,
|
||||
balancer: 0.34,
|
||||
curve: 0.29,
|
||||
one_inch: 0.48,
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
import { getCanonicalTokenBySymbol } from '../config/canonical-tokens';
|
||||
import { getCanonicalPriceUsd, resolveCanonicalPriceUsd, resolveCanonicalPriceUsdForSpec } from './canonical-price-oracle';
|
||||
|
||||
describe('canonical-price-oracle', () => {
|
||||
it('pegs Chain 138 USD-family mirrors to one dollar', () => {
|
||||
expect(getCanonicalPriceUsd(138, '0x71D6687F38b93CCad569Fa6352c876eea967201b')).toBe(1);
|
||||
expect(getCanonicalPriceUsd(138, '0x93E66202A11B1772E55407B32B44e5Cd8eda7f22')).toBe(1);
|
||||
expect(getCanonicalPriceUsd(138, '0xf22258f57794CC8E06237084b353Ab30fFfa640b')).toBe(1);
|
||||
});
|
||||
|
||||
it('anchors GRU v2 and lending wrappers to the matching c* ISO-4217 asset family', () => {
|
||||
const cUsdcV2 = getCanonicalTokenBySymbol(138, 'cUSDC_V2');
|
||||
expect(cUsdcV2?.addresses[138]).toBeTruthy();
|
||||
|
||||
expect(resolveCanonicalPriceUsd(138, String(cUsdcV2?.addresses[138]))).toMatchObject({
|
||||
priceUsd: 1,
|
||||
referenceSymbol: 'USD',
|
||||
});
|
||||
|
||||
expect(
|
||||
resolveCanonicalPriceUsdForSpec({
|
||||
symbol: 'acUSDC',
|
||||
name: 'Deposit cUSDC',
|
||||
type: 'asset',
|
||||
decimals: 6,
|
||||
addresses: { 138: '0xac00000000000000000000000000000000000138' },
|
||||
})
|
||||
).toMatchObject({
|
||||
priceUsd: 1,
|
||||
referenceSymbol: 'USD',
|
||||
});
|
||||
|
||||
expect(
|
||||
resolveCanonicalPriceUsdForSpec({
|
||||
symbol: 'vdcEURC',
|
||||
name: 'Debt cEURC (variable)',
|
||||
type: 'debt',
|
||||
decimals: 6,
|
||||
addresses: { 138: '0xbdc0000000000000000000000000000000000138' },
|
||||
})
|
||||
).toMatchObject({
|
||||
priceUsd: 1.1780,
|
||||
referenceSymbol: 'EUR',
|
||||
});
|
||||
|
||||
expect(
|
||||
resolveCanonicalPriceUsdForSpec({
|
||||
symbol: 'sdcCADC',
|
||||
name: 'Debt cCADC (stable)',
|
||||
type: 'debt',
|
||||
decimals: 6,
|
||||
addresses: { 138: '0xcdc0000000000000000000000000000000000138' },
|
||||
})
|
||||
).toMatchObject({
|
||||
priceUsd: 0.7255928549430243,
|
||||
referenceSymbol: 'CAD',
|
||||
});
|
||||
});
|
||||
|
||||
it('prices fiat GRU W tokens from the same ISO-4217 oracle references as the c* canonicals', () => {
|
||||
const usdw = getCanonicalTokenBySymbol(25, 'USDW');
|
||||
const eurw = getCanonicalTokenBySymbol(25, 'EURW');
|
||||
|
||||
expect(usdw?.addresses[25]).toBeTruthy();
|
||||
expect(eurw?.addresses[25]).toBeTruthy();
|
||||
|
||||
expect(resolveCanonicalPriceUsd(25, String(usdw?.addresses[25]))).toMatchObject({
|
||||
priceUsd: 1,
|
||||
referenceSymbol: 'USD',
|
||||
});
|
||||
expect(resolveCanonicalPriceUsd(25, String(eurw?.addresses[25]))).toMatchObject({
|
||||
priceUsd: 1.1780,
|
||||
referenceSymbol: 'EUR',
|
||||
});
|
||||
});
|
||||
|
||||
it('resolves WETH9 and WETH10 to the ETH peg with 18-decimal canonical metadata', () => {
|
||||
expect(resolveCanonicalPriceUsd(138, '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2')).toMatchObject({
|
||||
priceUsd: 2490,
|
||||
referenceSymbol: 'ETH',
|
||||
});
|
||||
expect(resolveCanonicalPriceUsd(138, '0xf4BB2e28688e89fCcE3c0580D37d36A7672E8A9f')).toMatchObject({
|
||||
priceUsd: 2490,
|
||||
referenceSymbol: 'ETH',
|
||||
});
|
||||
});
|
||||
|
||||
it('provides repo-local fallback pegs for commodity and monetary-unit canonicals', () => {
|
||||
expect(resolveCanonicalPriceUsd(138, '0x290E52a8819A4fbD0714E517225429aA2B70EC6b')).toMatchObject({
|
||||
referenceSymbol: 'XAU',
|
||||
source: 'repo-fallback',
|
||||
});
|
||||
expect(resolveCanonicalPriceUsd(138, '0xcb7c000000000000000000000000000000000138')).toMatchObject({
|
||||
priceUsd: 90000,
|
||||
referenceSymbol: 'BTC',
|
||||
source: 'repo-fallback',
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,193 @@
|
||||
import { type CanonicalTokenSpec, getCanonicalTokenByAddress, getCanonicalTokenBySymbol } from '../config/canonical-tokens';
|
||||
|
||||
export interface CanonicalPriceResolution {
|
||||
priceUsd?: number;
|
||||
referenceSymbol?: string;
|
||||
source: 'env' | 'repo-fallback' | 'unresolved';
|
||||
}
|
||||
|
||||
const FX_SNAPSHOT_GENERATED_AT = '2026-04-15';
|
||||
|
||||
// Repo-local inferred FX snapshot from scripts/lib/extraction_gap_closure.py.
|
||||
const REPO_FALLBACK_PRICE_USD: Record<string, number> = {
|
||||
USD: 1,
|
||||
EUR: 1.1780,
|
||||
GBP: 1.3550353712543854,
|
||||
AUD: 0.7136366390016357,
|
||||
CAD: 0.7255928549430243,
|
||||
CHF: 1.2776572668112798,
|
||||
JPY: 0.006285683794888213,
|
||||
XAU: 5163.3401260328355,
|
||||
ETH: 2490,
|
||||
BTC: 90000,
|
||||
BNB: 610,
|
||||
POL: 0.78,
|
||||
AVAX: 48,
|
||||
CELO: 0.72,
|
||||
CRO: 0.14,
|
||||
XDAI: 1,
|
||||
};
|
||||
|
||||
function normalizeAddress(value: string): string {
|
||||
return value.trim().toLowerCase();
|
||||
}
|
||||
|
||||
function readEnvPrice(keys: string[]): number | undefined {
|
||||
for (const key of keys) {
|
||||
const raw = process.env[key];
|
||||
if (!raw || raw.trim() === '') continue;
|
||||
const parsed = Number(raw);
|
||||
if (Number.isFinite(parsed) && parsed > 0) {
|
||||
return parsed;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function resolveReferenceSymbol(spec: CanonicalTokenSpec): string | undefined {
|
||||
const symbol = spec.symbol.toUpperCase();
|
||||
const currencyCode = String(spec.currencyCode || '').trim().toUpperCase();
|
||||
|
||||
if (
|
||||
symbol === 'WETH' ||
|
||||
symbol === 'WETH9' ||
|
||||
symbol === 'WETH10' ||
|
||||
symbol === 'CETH' ||
|
||||
symbol === 'CETHL2' ||
|
||||
symbol === 'CWETH' ||
|
||||
symbol === 'CWETHL2'
|
||||
) {
|
||||
return 'ETH';
|
||||
}
|
||||
|
||||
if (symbol === 'CBTC' || symbol === 'CWBTC') {
|
||||
return 'BTC';
|
||||
}
|
||||
|
||||
if (symbol === 'CXAUC' || symbol === 'CXAUT' || symbol === 'CAXAUC' || symbol === 'CAXAUT' || symbol === 'CWAXAUC' || symbol === 'CWAXAUT' || symbol === 'LIXAU') {
|
||||
return 'XAU';
|
||||
}
|
||||
|
||||
if (currencyCode) {
|
||||
return currencyCode;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function resolvePegSourceSpec(spec: CanonicalTokenSpec, seenSymbols: Set<string> = new Set()): CanonicalTokenSpec {
|
||||
const symbol = spec.symbol.trim();
|
||||
const symbolUpper = symbol.toUpperCase();
|
||||
if (seenSymbols.has(symbolUpper)) {
|
||||
return spec;
|
||||
}
|
||||
|
||||
const nextSeen = new Set(seenSymbols);
|
||||
nextSeen.add(symbolUpper);
|
||||
|
||||
const familySymbol = String(spec.familySymbol || '').trim();
|
||||
if (familySymbol) {
|
||||
const familyMatch = getCanonicalTokenByAddressFromSymbolFamily(spec, familySymbol);
|
||||
if (familyMatch) {
|
||||
return resolvePegSourceSpec(familyMatch, nextSeen);
|
||||
}
|
||||
}
|
||||
|
||||
const lendingWrapperMatch = /^(ac|vdc|sdc)(.+)$/i.exec(symbol);
|
||||
if (lendingWrapperMatch) {
|
||||
const underlyingSymbol = `c${lendingWrapperMatch[2]}`;
|
||||
const underlyingMatch = getCanonicalTokenByAddressFromSymbolFamily(spec, underlyingSymbol);
|
||||
if (underlyingMatch) {
|
||||
return resolvePegSourceSpec(underlyingMatch, nextSeen);
|
||||
}
|
||||
}
|
||||
|
||||
return spec;
|
||||
}
|
||||
|
||||
function getCanonicalTokenByAddressFromSymbolFamily(spec: CanonicalTokenSpec, symbol: string): CanonicalTokenSpec | undefined {
|
||||
for (const [chainIdText, address] of Object.entries(spec.addresses)) {
|
||||
if (!address || String(address).trim() === '') continue;
|
||||
const chainId = Number(chainIdText);
|
||||
if (!Number.isFinite(chainId)) continue;
|
||||
const familyMatch = getCanonicalTokenBySymbol(chainId, symbol);
|
||||
if (familyMatch) {
|
||||
return familyMatch;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function resolveEnvPriceKeys(referenceSymbol: string): string[] {
|
||||
const symbol = referenceSymbol.toUpperCase();
|
||||
if (symbol === 'XAU') {
|
||||
return [
|
||||
'CHAIN138_CANONICAL_PRICE_USD_XAU',
|
||||
'CANONICAL_PRICE_USD_XAU',
|
||||
'XAU_SPOT_USD',
|
||||
'GOLD_USD_PRICE',
|
||||
];
|
||||
}
|
||||
|
||||
if (symbol === 'ETH') {
|
||||
return [
|
||||
'CHAIN138_CANONICAL_PRICE_USD_ETH',
|
||||
'CANONICAL_PRICE_USD_ETH',
|
||||
'ETH_PRICE_USD',
|
||||
'CHAIN138_D3_PILOT_WETH_USD',
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
`CHAIN138_CANONICAL_PRICE_USD_${symbol}`,
|
||||
`CANONICAL_PRICE_USD_${symbol}`,
|
||||
`${symbol}_PRICE_USD`,
|
||||
];
|
||||
}
|
||||
|
||||
export function resolveCanonicalPriceUsdForSpec(spec: CanonicalTokenSpec): CanonicalPriceResolution {
|
||||
const pegSourceSpec = resolvePegSourceSpec(spec);
|
||||
const referenceSymbol = resolveReferenceSymbol(pegSourceSpec);
|
||||
if (!referenceSymbol) {
|
||||
return { source: 'unresolved' };
|
||||
}
|
||||
|
||||
const envPrice = readEnvPrice(resolveEnvPriceKeys(referenceSymbol));
|
||||
if (envPrice !== undefined) {
|
||||
return {
|
||||
priceUsd: envPrice,
|
||||
referenceSymbol,
|
||||
source: 'env',
|
||||
};
|
||||
}
|
||||
|
||||
const fallback = REPO_FALLBACK_PRICE_USD[referenceSymbol];
|
||||
if (fallback !== undefined) {
|
||||
return {
|
||||
priceUsd: fallback,
|
||||
referenceSymbol,
|
||||
source: 'repo-fallback',
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
referenceSymbol,
|
||||
source: 'unresolved',
|
||||
};
|
||||
}
|
||||
|
||||
export function resolveCanonicalPriceUsd(chainId: number, address: string): CanonicalPriceResolution {
|
||||
const spec = getCanonicalTokenByAddress(chainId, normalizeAddress(address));
|
||||
if (!spec) {
|
||||
return { source: 'unresolved' };
|
||||
}
|
||||
return resolveCanonicalPriceUsdForSpec(spec);
|
||||
}
|
||||
|
||||
export function getCanonicalPriceUsd(chainId: number, address: string): number | undefined {
|
||||
return resolveCanonicalPriceUsd(chainId, address).priceUsd;
|
||||
}
|
||||
|
||||
export function getCanonicalPriceSnapshotGeneratedAt(): string {
|
||||
return FX_SNAPSHOT_GENERATED_AT;
|
||||
}
|
||||
@@ -28,7 +28,20 @@ describe('estimateChain138DodoLiquidityUsd', () => {
|
||||
expect(result.totalLiquidityUsd).toBe(210_830);
|
||||
});
|
||||
|
||||
it('keeps non-USD pairs at zero without a usable USD side', () => {
|
||||
it('keeps WETH9 on the ETH peg even when live oracle price is unavailable', () => {
|
||||
const result = estimateChain138DodoLiquidityUsd({
|
||||
token0Address: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2',
|
||||
token1Address: '0x004b63A7B5b0E06f6bB6adb4a5F9f590BF3182D1',
|
||||
reserve0: 10n * 10n ** 18n,
|
||||
reserve1: 24_900n * 10n ** 6n,
|
||||
});
|
||||
|
||||
expect(result.reserve0Usd).toBe(24_900);
|
||||
expect(result.reserve1Usd).toBe(24_900);
|
||||
expect(result.totalLiquidityUsd).toBe(49_800);
|
||||
});
|
||||
|
||||
it('values non-USD canonical pairs from their repo-local peg references', () => {
|
||||
const result = estimateChain138DodoLiquidityUsd({
|
||||
token0Address: '0xf4BB2e28688e89fCcE3c0580D37d36A7672E8A9f',
|
||||
token1Address: '0x290E52a8819A4fbD0714E517225429aA2B70EC6b',
|
||||
@@ -36,11 +49,22 @@ describe('estimateChain138DodoLiquidityUsd', () => {
|
||||
reserve1: 5n * 10n ** 6n,
|
||||
});
|
||||
|
||||
expect(result).toEqual({
|
||||
reserve0Usd: 0,
|
||||
reserve1Usd: 0,
|
||||
totalLiquidityUsd: 0,
|
||||
expect(result.reserve0Usd).toBe(24_900);
|
||||
expect(result.reserve1Usd).toBeCloseTo(25_816.700630164178, 6);
|
||||
expect(result.totalLiquidityUsd).toBeCloseTo(50_716.70063016418, 6);
|
||||
});
|
||||
|
||||
it('values XAU/stable DODO pools from the canonical gold peg', () => {
|
||||
const result = estimateChain138DodoLiquidityUsd({
|
||||
token0Address: '0x290E52a8819A4fbD0714E517225429aA2B70EC6b',
|
||||
token1Address: '0x004b63A7B5b0E06f6bB6adb4a5F9f590BF3182D1',
|
||||
reserve0: 5n * 10n ** 6n,
|
||||
reserve1: 25_816n * 10n ** 6n,
|
||||
});
|
||||
|
||||
expect(result.reserve0Usd).toBeCloseTo(25_816.700630164178, 6);
|
||||
expect(result.reserve1Usd).toBe(25_816);
|
||||
expect(result.totalLiquidityUsd).toBeCloseTo(51_632.70063016418, 6);
|
||||
});
|
||||
|
||||
it('values cBTC/stable DODO pools using satoshi precision and the BTC fallback price', () => {
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import { formatUnits } from 'ethers';
|
||||
import { getCanonicalTokenByAddress } from '../config/canonical-tokens';
|
||||
import { getCanonicalPriceUsd } from './canonical-price-oracle';
|
||||
|
||||
const CHAIN_138 = 138;
|
||||
const DEFAULT_WETH_USD_PRICE = 2100;
|
||||
const DEFAULT_BTC_USD_PRICE = 90000;
|
||||
|
||||
export interface Chain138DodoLiquidityUsd {
|
||||
reserve0Usd: number;
|
||||
@@ -20,21 +19,6 @@ function decimalsForAddress(address: string): number {
|
||||
return Number(spec?.decimals ?? 18);
|
||||
}
|
||||
|
||||
function isUsdAddress(address: string): boolean {
|
||||
const spec = getCanonicalTokenByAddress(CHAIN_138, normalizeAddress(address));
|
||||
return spec?.currencyCode === 'USD';
|
||||
}
|
||||
|
||||
function isWethLikeAddress(address: string): boolean {
|
||||
const symbol = getCanonicalTokenByAddress(CHAIN_138, normalizeAddress(address))?.symbol?.toUpperCase();
|
||||
return symbol === 'WETH' || symbol === 'WETH10';
|
||||
}
|
||||
|
||||
function isBtcLikeAddress(address: string): boolean {
|
||||
const symbol = getCanonicalTokenByAddress(CHAIN_138, normalizeAddress(address))?.symbol?.toUpperCase();
|
||||
return symbol === 'CBTC';
|
||||
}
|
||||
|
||||
function parseAmount(value: bigint, decimals: number): number {
|
||||
if (value <= 0n) return 0;
|
||||
const parsed = Number(formatUnits(value, decimals));
|
||||
@@ -60,54 +44,32 @@ export function estimateChain138DodoLiquidityUsd(args: {
|
||||
const reserve1Amount = parseAmount(args.reserve1, decimalsForAddress(token1Address));
|
||||
const price = parsePrice(args.price);
|
||||
|
||||
const token0IsUsd = isUsdAddress(token0Address);
|
||||
const token1IsUsd = isUsdAddress(token1Address);
|
||||
|
||||
if (reserve0Amount <= 0 || reserve1Amount <= 0) {
|
||||
return { reserve0Usd: 0, reserve1Usd: 0, totalLiquidityUsd: 0 };
|
||||
}
|
||||
|
||||
if (token0IsUsd && token1IsUsd) {
|
||||
return {
|
||||
reserve0Usd: reserve0Amount,
|
||||
reserve1Usd: reserve1Amount,
|
||||
totalLiquidityUsd: reserve0Amount + reserve1Amount,
|
||||
};
|
||||
let token0PriceUsd = getCanonicalPriceUsd(CHAIN_138, token0Address) ?? 0;
|
||||
let token1PriceUsd = getCanonicalPriceUsd(CHAIN_138, token1Address) ?? 0;
|
||||
|
||||
if (price > 0) {
|
||||
if (token1PriceUsd === 1 && token0PriceUsd !== 1) {
|
||||
token0PriceUsd = price;
|
||||
}
|
||||
if (token0PriceUsd === 1 && token1PriceUsd !== 1) {
|
||||
token1PriceUsd = price;
|
||||
}
|
||||
}
|
||||
|
||||
if (token1IsUsd) {
|
||||
const reserve0Usd =
|
||||
price > 0
|
||||
? reserve0Amount * price
|
||||
: isWethLikeAddress(token0Address)
|
||||
? reserve0Amount * DEFAULT_WETH_USD_PRICE
|
||||
: isBtcLikeAddress(token0Address)
|
||||
? reserve0Amount * DEFAULT_BTC_USD_PRICE
|
||||
: 0;
|
||||
|
||||
return {
|
||||
reserve0Usd,
|
||||
reserve1Usd: reserve1Amount,
|
||||
totalLiquidityUsd: reserve0Usd > 0 ? reserve0Usd + reserve1Amount : 0,
|
||||
};
|
||||
if (token0PriceUsd <= 0 || token1PriceUsd <= 0) {
|
||||
return { reserve0Usd: 0, reserve1Usd: 0, totalLiquidityUsd: 0 };
|
||||
}
|
||||
|
||||
if (token0IsUsd) {
|
||||
const reserve1Usd =
|
||||
price > 0
|
||||
? reserve1Amount / price
|
||||
: isWethLikeAddress(token1Address)
|
||||
? reserve1Amount * DEFAULT_WETH_USD_PRICE
|
||||
: isBtcLikeAddress(token1Address)
|
||||
? reserve1Amount * DEFAULT_BTC_USD_PRICE
|
||||
: 0;
|
||||
const reserve0Usd = reserve0Amount * token0PriceUsd;
|
||||
const reserve1Usd = reserve1Amount * token1PriceUsd;
|
||||
|
||||
return {
|
||||
reserve0Usd: reserve0Amount,
|
||||
reserve1Usd,
|
||||
totalLiquidityUsd: reserve1Usd > 0 ? reserve0Amount + reserve1Usd : 0,
|
||||
};
|
||||
}
|
||||
|
||||
return { reserve0Usd: 0, reserve1Usd: 0, totalLiquidityUsd: 0 };
|
||||
return {
|
||||
reserve0Usd,
|
||||
reserve1Usd,
|
||||
totalLiquidityUsd: reserve0Usd + reserve1Usd,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,4 +1,13 @@
|
||||
export type PlannerProvider = 'dodo' | 'dodo_v3' | 'uniswap_v3' | 'balancer' | 'curve' | 'one_inch' | 'partner';
|
||||
export type PlannerProvider =
|
||||
| 'dodo'
|
||||
| 'dodo_v3'
|
||||
| 'uniswap_v3'
|
||||
| 'uniswap_v2'
|
||||
| 'sushiswap'
|
||||
| 'balancer'
|
||||
| 'curve'
|
||||
| 'one_inch'
|
||||
| 'partner';
|
||||
export type PlannerLegKind = 'swap' | 'bridge';
|
||||
export type PlannerDecision = 'direct-pool' | 'multi-hop' | 'swap-bridge-swap' | 'bridge-only' | 'unresolved';
|
||||
export type ComplianceProfile = 'standard' | 'institutional';
|
||||
|
||||
@@ -30,6 +30,10 @@ function providerFromDexType(dexType: string): PlannerProvider | null {
|
||||
return 'dodo_v3';
|
||||
case 'uniswap_v3':
|
||||
return 'uniswap_v3';
|
||||
case 'uniswap_v2':
|
||||
return 'uniswap_v2';
|
||||
case 'sushiswap':
|
||||
return 'sushiswap';
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user