|
1 | | -# Gas Price Oracle |
| 1 | +# Token Price Oracle |
2 | 2 |
|
3 | | -Gas Price Oracle service monitors L1 gas prices and updates the GasPriceOracle contract on L2. |
| 3 | +Token Price Oracle service monitors token prices and updates the price ratio between tokens and ETH to L2 on-chain contracts, enabling Alt Fee Token functionality. |
4 | 4 |
|
5 | 5 | ## Features |
6 | 6 |
|
7 | | -- **L1 Base Fee Update**: Monitors L1 base fee and blob base fee, updates to L2 |
8 | | -- **Scalar Update**: Calculates and updates commit scalar and blob scalar |
9 | | -- **Transaction Manager**: Serializes all contract updates to avoid nonce conflicts |
10 | | -- **Metrics Monitoring**: Exposes Prometheus metrics |
11 | | -- **Flags Configuration**: Uses `urfave/cli` for configuration management (supports both CLI flags and environment variables) |
| 7 | +- **Real-time Price Monitoring**: Fetches token USD prices from exchange APIs (Bitget) |
| 8 | +- **Price Ratio Calculation**: Computes price ratio between tokens and ETH |
| 9 | +- **Threshold-based Updates**: Only updates on-chain when price change exceeds threshold, saving Gas |
| 10 | +- **Batch Updates**: Updates multiple token prices in a single `batchUpdatePrices` transaction |
| 11 | +- **Fallback Mechanism**: Supports automatic switching between multiple data sources |
| 12 | +- **Transaction Management**: Prevents nonce conflicts, supports local and external signing |
| 13 | +- **Prometheus Monitoring**: Provides operational metrics |
12 | 14 |
|
13 | | -## Configuration |
14 | | - |
15 | | -The service uses flags that can be set either via command line or environment variables (with `GAS_ORACLE_` prefix). |
16 | | - |
17 | | -### Required Flags |
18 | | - |
19 | | -| Flag | Env Var | Description | |
20 | | -| --------------------- | --------------------------- | ------------------------------- | |
21 | | -| `--l1-eth-rpc` | `GAS_ORACLE_L1_ETH_RPC` | L1 RPC endpoint | |
22 | | -| `--l2-eth-rpc` | `GAS_ORACLE_L2_ETH_RPC` | L2 RPC endpoint | |
23 | | -| `--l1-beacon-rpc` | `GAS_ORACLE_L1_BEACON_RPC` | L1 Beacon Chain API endpoint | |
24 | | -| `--l1-rollup-address` | `GAS_ORACLE_L1_ROLLUP` | L1 Rollup contract address | |
25 | | -| `--private-key` | `GAS_ORACLE_L2_PRIVATE_KEY` | Private key for L2 transactions | |
26 | | - |
27 | | -### Optional Flags |
| 15 | +## Quick Start |
28 | 16 |
|
29 | | -| Flag | Env Var | Default | Description | |
30 | | -| ------------------------------- | ---------------------------------- | --------------- | --------------------------- | |
31 | | -| `--l2-gas-price-oracle-address` | `GAS_ORACLE_L2_GAS_PRICE_ORACLE` | `0x5300...0002` | L2 GasPriceOracle contract | |
32 | | -| `--gas-threshold` | `GAS_ORACLE_GAS_THRESHOLD` | `10` | Update threshold percentage | |
33 | | -| `--interval` | `GAS_ORACLE_INTERVAL` | `6s` | Base fee update interval | |
34 | | -| `--overhead-interval` | `GAS_ORACLE_OVERHEAD_INTERVAL` | `10` | Scalar update frequency | |
35 | | -| `--txn-per-batch` | `GAS_ORACLE_TXN_PER_BATCH` | `50` | Expected txs per batch | |
36 | | -| `--log-level` | `GAS_ORACLE_LOG_LEVEL` | `info` | Log level | |
37 | | -| `--log-filename` | `GAS_ORACLE_LOG_FILENAME` | - | Log file path | |
38 | | -| `--metrics-server-enable` | `GAS_ORACLE_METRICS_SERVER_ENABLE` | `false` | Enable metrics server | |
39 | | -| `--metrics-hostname` | `GAS_ORACLE_METRICS_HOSTNAME` | `0.0.0.0` | Metrics server host | |
40 | | -| `--metrics-port` | `GAS_ORACLE_METRICS_PORT` | `6060` | Metrics server port | |
41 | | - |
42 | | -## Usage |
43 | | - |
44 | | -### Command Line |
| 17 | +### Environment Variables (Local Signing Mode) |
45 | 18 |
|
46 | 19 | ```bash |
47 | | -./bin/token-price-oracle \ |
48 | | - --l1-eth-rpc https://ethereum-rpc.com \ |
49 | | - --l2-eth-rpc https://morph-l2-rpc.com \ |
50 | | - --l1-beacon-rpc https://beacon-api.com \ |
51 | | - --l1-rollup-address 0x... \ |
52 | | - --private-key 0x... \ |
53 | | - --metrics-server-enable \ |
54 | | - --log-level debug |
| 20 | +# Required |
| 21 | +export TOKEN_PRICE_ORACLE_L2_ETH_RPC="https://rpc.morphl2.io" |
| 22 | +export TOKEN_PRICE_ORACLE_PRIVATE_KEY="0x..." # Required for local signing only |
| 23 | +export TOKEN_PRICE_ORACLE_BITGET_API_BASE_URL="https://api.bitget.com" |
| 24 | +export TOKEN_PRICE_ORACLE_TOKEN_MAPPING_BITGET="1:BTCUSDT,2:ETHUSDT" |
| 25 | + |
| 26 | +# Optional |
| 27 | +export TOKEN_PRICE_ORACLE_PRICE_UPDATE_INTERVAL="1m" |
| 28 | +export TOKEN_PRICE_ORACLE_PRICE_THRESHOLD="100" # 1% (100 bps) |
| 29 | +export TOKEN_PRICE_ORACLE_METRICS_SERVER_ENABLE="true" |
| 30 | +export TOKEN_PRICE_ORACLE_METRICS_PORT="6060" |
| 31 | +export TOKEN_PRICE_ORACLE_LOG_LEVEL="info" |
55 | 32 | ``` |
56 | 33 |
|
57 | | -### Environment Variables |
58 | | - |
59 | | -```bash |
60 | | -export GAS_ORACLE_L1_ETH_RPC="https://ethereum-rpc.com" |
61 | | -export GAS_ORACLE_L2_ETH_RPC="https://morph-l2-rpc.com" |
62 | | -export GAS_ORACLE_L1_BEACON_RPC="https://beacon-api.com" |
63 | | -export GAS_ORACLE_L1_ROLLUP="0x..." |
64 | | -export GAS_ORACLE_L2_PRIVATE_KEY="0x..." |
65 | | -export GAS_ORACLE_METRICS_SERVER_ENABLE=true |
66 | | -export GAS_ORACLE_LOG_LEVEL=info |
67 | | - |
68 | | -./bin/token-price-oracle |
69 | | -``` |
| 34 | +> **Note**: `PRIVATE_KEY` is only required when using local signing mode. For production, use [External Signing](#external-signing-recommended-for-production) instead. |
70 | 35 |
|
71 | | -## Build and Run |
72 | | - |
73 | | -**Note**: This project uses Go workspace and depends on `../bindings` module. |
| 36 | +### Build and Run |
74 | 37 |
|
75 | 38 | ```bash |
76 | 39 | # Build |
77 | 40 | make build |
78 | 41 |
|
79 | 42 | # Run |
80 | | -make run |
81 | | - |
82 | | -# Test |
83 | | -make test |
| 43 | +./build/bin/token-price-oracle |
84 | 44 |
|
85 | | -# Test Bitget price feed (requires network) |
86 | | -go test ./client -run TestBitgetPriceFeed -v |
87 | | - |
88 | | -# Docker |
| 45 | +# Or use Docker |
89 | 46 | make docker-build |
90 | 47 | docker run -d \ |
91 | | - -e GAS_ORACLE_L1_ETH_RPC="..." \ |
92 | | - -e GAS_ORACLE_L2_ETH_RPC="..." \ |
93 | | - -e GAS_ORACLE_L1_BEACON_RPC="..." \ |
94 | | - -e GAS_ORACLE_L1_ROLLUP="0x..." \ |
95 | | - -e GAS_ORACLE_L2_PRIVATE_KEY="0x..." \ |
| 48 | + -e TOKEN_PRICE_ORACLE_L2_ETH_RPC="..." \ |
| 49 | + -e TOKEN_PRICE_ORACLE_PRIVATE_KEY="..." \ |
| 50 | + -e TOKEN_PRICE_ORACLE_BITGET_API_BASE_URL="..." \ |
| 51 | + -e TOKEN_PRICE_ORACLE_TOKEN_MAPPING_BITGET="..." \ |
96 | 52 | morph/token-price-oracle:latest |
97 | 53 | ``` |
98 | 54 |
|
99 | | -## Monitoring |
| 55 | +## Configuration |
100 | 56 |
|
101 | | -When metrics server is enabled, it exposes metrics at `<hostname>:<port>/metrics`: |
| 57 | +### Required (All Modes) |
102 | 58 |
|
103 | | -- `l1_base_fee` - L1 base fee (Gwei) |
104 | | -- `l1_base_fee_on_l2` - L1 base fee on L2 |
105 | | -- `l1_blob_base_fee_on_l2` - L1 blob base fee on L2 |
106 | | -- `commit_scalar` - Commit scalar value |
107 | | -- `blob_scalar` - Blob scalar value |
108 | | -- `txn_per_batch` - Transactions per batch |
109 | | -- `gas_oracle_owner_balance` - Oracle account balance |
110 | | -- `base_fee_update_count` - Total base fee updates |
111 | | -- `scalar_update_count` - Total scalar updates |
112 | | -- `update_errors_total` - Update errors by type |
| 59 | +| Environment Variable | Description | |
| 60 | +|---------------------|-------------| |
| 61 | +| `TOKEN_PRICE_ORACLE_L2_ETH_RPC` | L2 node RPC endpoint | |
| 62 | +| `TOKEN_PRICE_ORACLE_BITGET_API_BASE_URL` | Bitget API base URL | |
| 63 | +| `TOKEN_PRICE_ORACLE_TOKEN_MAPPING_BITGET` | TokenID to trading pair mapping | |
113 | 64 |
|
114 | | -Health check endpoint: `<hostname>:<port>/health` |
| 65 | +### Required (Local Signing Mode Only) |
115 | 66 |
|
116 | | -## Architecture |
| 67 | +| Environment Variable | Description | |
| 68 | +|---------------------|-------------| |
| 69 | +| `TOKEN_PRICE_ORACLE_PRIVATE_KEY` | Signing private key (not needed if using external signing) | |
117 | 70 |
|
118 | | -``` |
119 | | -gas-price-oracle/ |
120 | | -├── cmd/ # Main entry point |
121 | | -├── flags/ # CLI flags definitions |
122 | | -├── config/ # Configuration from flags |
123 | | -├── updater/ # Update implementations |
124 | | -│ ├── basefee.go # Base fee updater |
125 | | -│ ├── scalar.go # Scalar updater |
126 | | -│ └── tx_manager.go # Transaction manager (prevents nonce conflicts) |
127 | | -├── client/ # Client wrappers |
128 | | -├── calc/ # Calculation logic |
129 | | -└── metrics/ # Prometheus metrics |
| 71 | +### Optional |
| 72 | + |
| 73 | +| Environment Variable | Default | Description | |
| 74 | +|---------------------|---------|-------------| |
| 75 | +| `TOKEN_PRICE_ORACLE_PRICE_UPDATE_INTERVAL` | `1m` | Price update interval | |
| 76 | +| `TOKEN_PRICE_ORACLE_PRICE_THRESHOLD` | `100` | Update threshold (basis points, 100=1%) | |
| 77 | +| `TOKEN_PRICE_ORACLE_PRICE_FEED_PRIORITY` | `bitget` | Price feed priority | |
| 78 | +| `TOKEN_PRICE_ORACLE_METRICS_SERVER_ENABLE` | `false` | Enable metrics server | |
| 79 | +| `TOKEN_PRICE_ORACLE_METRICS_HOSTNAME` | `0.0.0.0` | Metrics server hostname | |
| 80 | +| `TOKEN_PRICE_ORACLE_METRICS_PORT` | `6060` | Metrics server port | |
| 81 | +| `TOKEN_PRICE_ORACLE_LOG_LEVEL` | `info` | Log level | |
| 82 | +| `TOKEN_PRICE_ORACLE_LOG_FILENAME` | - | Log file path | |
| 83 | + |
| 84 | +### External Signing (Recommended for Production) |
| 85 | + |
| 86 | +| Environment Variable | Description | |
| 87 | +|---------------------|-------------| |
| 88 | +| `TOKEN_PRICE_ORACLE_EXTERNAL_SIGN` | Enable external signing (`true`/`false`) | |
| 89 | +| `TOKEN_PRICE_ORACLE_EXTERNAL_SIGN_ADDRESS` | Signing account address | |
| 90 | +| `TOKEN_PRICE_ORACLE_EXTERNAL_SIGN_APPID` | External signing service AppID | |
| 91 | +| `TOKEN_PRICE_ORACLE_EXTERNAL_SIGN_CHAIN` | Chain identifier | |
| 92 | +| `TOKEN_PRICE_ORACLE_EXTERNAL_SIGN_URL` | External signing service URL | |
| 93 | +| `TOKEN_PRICE_ORACLE_EXTERNAL_SIGN_RSA_PRIV` | RSA private key (PEM format) | |
| 94 | + |
| 95 | +## Price Calculation |
130 | 96 |
|
131 | | -Uses: ../bindings/bindings (project root contract bindings) |
| 97 | +### Price Ratio Formula |
| 98 | + |
| 99 | +``` |
| 100 | +priceRatio = tokenScale × tokenPriceUSD × 10^(18 - tokenDecimals) / ethPriceUSD |
132 | 101 | ``` |
133 | 102 |
|
134 | | -## Key Components |
| 103 | +### Threshold |
135 | 104 |
|
136 | | -### Transaction Manager |
| 105 | +Threshold is specified in basis points (bps): |
| 106 | +- 1 bps = 0.01% |
| 107 | +- 100 bps = 1% |
| 108 | +- 10000 bps = 100% |
137 | 109 |
|
138 | | -All contract updates are serialized through `TxManager` to prevent nonce conflicts: |
| 110 | +On-chain prices are only updated when price change exceeds the threshold, avoiding unnecessary Gas costs. |
139 | 111 |
|
140 | | -- Holds a mutex to ensure only one transaction is sent at a time |
141 | | -- Manages nonce retrieval and transaction confirmation |
142 | | -- Used by both `BaseFeeUpdater` and `ScalarUpdater` |
| 112 | +## Monitoring |
143 | 113 |
|
144 | | -### Base Fee Updater |
| 114 | +### Prometheus Metrics |
145 | 115 |
|
146 | | -- Runs on a fixed interval (default 6s) |
147 | | -- Fetches L1 base fee and blob base fee |
148 | | -- Updates L2 contract when threshold is exceeded |
| 116 | +When metrics server is enabled, access `http://<host>:<port>/metrics`: |
149 | 117 |
|
150 | | -### Scalar Updater |
| 118 | +| Metric | Type | Description | |
| 119 | +|--------|------|-------------| |
| 120 | +| `last_successful_update_timestamp` | Gauge | Last successful update timestamp | |
| 121 | +| `updates_total{type="updated"}` | Counter | Actual update count | |
| 122 | +| `updates_total{type="skipped"}` | Counter | Skipped update count | |
| 123 | +| `update_errors_total{type="price"}` | Counter | Update error count | |
| 124 | +| `account_balance_eth` | Gauge | Oracle account balance | |
151 | 125 |
|
152 | | -- Runs every N base fee update cycles (default 10) |
153 | | -- Reads `CommitBatch` events from L1 Rollup |
154 | | -- Calculates commit and blob scalars |
155 | | -- Updates L2 contract when necessary |
| 126 | +### Health Check |
156 | 127 |
|
157 | | -### Blob Processing |
| 128 | +```bash |
| 129 | +curl http://<host>:<port>/health |
| 130 | +``` |
158 | 131 |
|
159 | | -Blob data processing is partially implemented (interface defined in `calc/blob.go`). The actual blob parsing and L2 transaction extraction is deferred for future implementation. |
| 132 | +### Suggested Alert Rules |
| 133 | + |
| 134 | +```yaml |
| 135 | +# Price not updated for a long time |
| 136 | +- alert: TokenPriceOracleStalled |
| 137 | + expr: time() - last_successful_update_timestamp > 300 |
| 138 | + for: 1m |
| 139 | + labels: |
| 140 | + severity: critical |
| 141 | + |
| 142 | +# Low account balance |
| 143 | +- alert: TokenPriceOracleLowBalance |
| 144 | + expr: account_balance_eth < 0.1 |
| 145 | + for: 5m |
| 146 | + labels: |
| 147 | + severity: warning |
| 148 | +``` |
| 149 | +
|
| 150 | +## Project Structure |
160 | 151 |
|
161 | | -## Testing |
| 152 | +``` |
| 153 | +token-price-oracle/ |
| 154 | +├── cmd/ # Entry point |
| 155 | +├── flags/ # CLI flags definition |
| 156 | +├── config/ # Configuration loading |
| 157 | +├── client/ # Client wrappers |
| 158 | +│ ├── l2_client.go # L2 chain client |
| 159 | +│ ├── price_feed.go # Price feed interface |
| 160 | +│ ├── bitget_sdk.go # Bitget API client |
| 161 | +│ └── sign.go # External signing |
| 162 | +├── updater/ # Update logic |
| 163 | +│ ├── token_price.go # Price updater |
| 164 | +│ ├── tx_manager.go # Transaction manager |
| 165 | +│ └── factory.go # Factory methods |
| 166 | +├── metrics/ # Prometheus metrics |
| 167 | +└── README.md # This document |
| 168 | +``` |
| 169 | + |
| 170 | +## Development |
162 | 171 |
|
163 | 172 | ```bash |
164 | | -# Run all tests |
165 | | -go test ./... |
| 173 | +# Run tests |
| 174 | +make test |
166 | 175 |
|
167 | 176 | # Test Bitget price feed (requires network) |
168 | 177 | go test ./client -run TestBitgetPriceFeed -v |
169 | 178 |
|
170 | | -# Skip integration tests |
171 | | -go test ./... -short |
| 179 | +# Format code |
| 180 | +go fmt ./... |
| 181 | + |
| 182 | +# Local run |
| 183 | +cp env.example .env |
| 184 | +# Edit .env configuration |
| 185 | +source .env && make run |
172 | 186 | ``` |
173 | 187 |
|
| 188 | +## License |
| 189 | + |
| 190 | +MIT |
0 commit comments