Skip to content
Draft
Show file tree
Hide file tree
Changes from 43 commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
78e1a56
feat: :sparkles: initial attempt
timbrinded Nov 4, 2025
02eac5d
Merge branch 'master' of github.com:Moonsong-Labs/tq-oracle into feat…
timbrinded Nov 5, 2025
479a013
Fix Tests
timbrinded Nov 5, 2025
5c0bcc5
fixed stakewise support
timbrinded Nov 5, 2025
3bf8640
minor progress
timbrinded Nov 5, 2025
5ff65e5
first attempt
timbrinded Nov 7, 2025
c8b9f02
tidy
timbrinded Nov 7, 2025
296d558
tidy
timbrinded Nov 7, 2025
3f2bfe9
refactor out leveraged logic
timbrinded Nov 10, 2025
430513f
tidy
timbrinded Nov 10, 2025
58d8a35
new options
timbrinded Nov 10, 2025
74263d1
config schema change
timbrinded Nov 10, 2025
df1607f
tidy
timbrinded Nov 10, 2025
5bf20a9
update deps
timbrinded Nov 10, 2025
f0eff94
pr comment fixes
timbrinded Nov 10, 2025
6ca299d
tidy
timbrinded Nov 10, 2025
daf1a79
tidy assets
timbrinded Nov 10, 2025
435eb60
Merge branch 'master' of github.com:Moonsong-Labs/tq-oracle into feat…
timbrinded Nov 10, 2025
a63d765
tidy
timbrinded Nov 10, 2025
982de6b
Merge branch 'master' of github.com:Moonsong-Labs/tq-oracle into fix/…
timbrinded Nov 21, 2025
3b3fd56
refactor: :recycle: Tidy pointless check (#111)
timbrinded Nov 21, 2025
d9de131
feat: :sparkles: Add pyth failure toggles (#112)
timbrinded Nov 21, 2025
13d049c
fix: :bug: Make block number consistent across all RPC calls (#113)
timbrinded Nov 21, 2025
93314e6
feat: :sparkles: add global timeout (#116)
timbrinded Nov 21, 2025
dac25d2
make failed confidence checks raise errors (#115)
timbrinded Nov 21, 2025
f6b10ed
chore: rm unused scale fn (#114)
timbrinded Nov 21, 2025
e4e0865
feat: :zap: added backoff retries (#110)
timbrinded Nov 21, 2025
054285a
fix: :wrench: tighten empty vault error handling (#106)
timbrinded Nov 21, 2025
7b27b98
feat: :sparkles: Add post check retry (#105)
timbrinded Nov 21, 2025
db792c1
fix: :bug: use proper timestamp for timeout check (#103)
timbrinded Nov 21, 2025
3b556bd
add sus report shortcircuit (#102)
timbrinded Nov 21, 2025
b0a43d0
fix: replace broad exception handling with specific exceptions (#100)
matias-gonz Nov 21, 2025
682b41a
fix: :bug: collect dust (#97)
timbrinded Nov 21, 2025
e3b0ecb
refactor: :recycle: tidy code (#96)
timbrinded Nov 21, 2025
3ce7240
fix: 🛠️ Correct price scaling across adapters (#108)
timbrinded Nov 21, 2025
05044d5
refactor: :recycle: tidy code (#104)
timbrinded Nov 21, 2025
234f883
fix: add missing timeouts to HTTP requests (#101)
matias-gonz Nov 21, 2025
2bb0d44
refactor: :recycle: tidy code (#95)
timbrinded Nov 21, 2025
3229b11
fix: add Pyth API response validation (#99)
matias-gonz Nov 21, 2025
2e2fd9d
feat: confidence check fails if confidence ratio exceeds maximum in P…
timbrinded Nov 21, 2025
ddd655e
fix: add CowSwap API response validation (#98)
matias-gonz Nov 21, 2025
eb9b801
Fix/final report dps (#119)
timbrinded Nov 23, 2025
2bb4bfe
Fix/final report dps (#120)
timbrinded Nov 23, 2025
e5f5ce6
Fix/final report dps (#121)
timbrinded Nov 23, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ All configuration options can be set via CLI arguments, environment variables, o
| `--ignore-timeout-check/--enforce-timeout-check` | `TQ_ORACLE_IGNORE_TIMEOUT_CHECK` | `ignore_timeout_check` | `false` | Skip minimum interval guard between reports |
| `--ignore-active-proposal-check/--enforce-active-proposal-check` | `TQ_ORACLE_IGNORE_ACTIVE_PROPOSAL_CHECK` | `ignore_active_proposal_check` | `false` | Skip duplicate active proposal guard |
| `--log-level` | `TQ_ORACLE_LOG_LEVEL` | `log_level` | `"INFO"` | Override logging verbosity (`TRACE`, `DEBUG`, `INFO`, `WARNING`, `ERROR`, `CRITICAL`) |
| `--global-timeout-seconds` | `TQ_ORACLE_GLOBAL_TIMEOUT_SECONDS` | `global_timeout_seconds` | `600.0` | Maximum seconds allowed for the full pipeline (set `0` to disable) |
| `--show-config` | - | - | `false` | Dump effective configuration (with secrets redacted) and exit |

#### TOML-Only Options (Not available via CLI)
Expand Down
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ dev = [
"pytest>=8.4.2",
"pytest-asyncio>=1.2.0",
"pytest-env>=1.1.0",
"pytest-mock>=3.15.1",
"ruff>=0.13.2",
]

Expand Down
11 changes: 9 additions & 2 deletions src/tq_oracle/abi.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
from eth_typing import URI, ChecksumAddress
from web3 import Web3

from tq_oracle.settings import OracleSettings

ABIS_DIR = Path(__file__).parent / "abis"

ORACLE_ABI_PATH = ABIS_DIR / "IOracle.json"
Expand Down Expand Up @@ -85,7 +87,7 @@ def load_stakewise_os_token_vault_escrow_abi() -> list[dict]:
return load_abi(STAKEWISE_OS_TOKEN_VAULT_ESCROW_ABI_PATH)


def get_oracle_address_from_vault(vault_address: str, rpc_url: str) -> ChecksumAddress:
def get_oracle_address_from_vault(settings: OracleSettings) -> ChecksumAddress:
"""Fetch the oracle address from the vault contract.

Args:
Expand All @@ -99,6 +101,9 @@ def get_oracle_address_from_vault(vault_address: str, rpc_url: str) -> ChecksumA
ConnectionError: If RPC connection fails
ValueError: If contract call fails
"""
rpc_url = settings.vault_rpc_required
vault_address = settings.vault_address_required
block_number = settings.block_number_required
w3 = Web3(Web3.HTTPProvider(URI(rpc_url)))
if not w3.is_connected():
raise ConnectionError(f"Failed to connect to RPC: {rpc_url}")
Expand All @@ -108,7 +113,9 @@ def get_oracle_address_from_vault(vault_address: str, rpc_url: str) -> ChecksumA
vault_contract = w3.eth.contract(address=checksum_vault, abi=vault_abi)

try:
oracle_addr: ChecksumAddress = vault_contract.functions.oracle().call()
oracle_addr: ChecksumAddress = vault_contract.functions.oracle().call(
block_identifier=block_number
)
return oracle_addr
except Exception as e:
raise ValueError(f"Failed to fetch oracle address from vault: {e}") from e
17 changes: 11 additions & 6 deletions src/tq_oracle/adapters/asset_adapters/idle_balances.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,9 @@ async def _rpc(self, fn, *args, **kwargs):
def adapter_name(self) -> str:
return "idle_balances"

async def fetch_assets(self, subvault_address: str) -> list[AssetData]:
async def fetch_assets(
self, subvault_address: str, *, supported_assets: list[str] | None = None
) -> list[AssetData]:
"""Fetch idle balances for the given subvault on the configured chain.

Args:
Expand All @@ -115,7 +117,8 @@ async def fetch_assets(self, subvault_address: str) -> list[AssetData]:
Returns:
List of AssetData objects containing asset addresses and balances
"""
supported_assets = await self._fetch_supported_assets()
if supported_assets is None:
supported_assets = await self._fetch_supported_assets()

logger.debug(
"Fetching L1 balances for subvault %s across %d assets",
Expand Down Expand Up @@ -161,9 +164,13 @@ async def fetch_all_assets(self) -> list[AssetData]:
len(subvault_addresses),
len(vault_addresses) - 1 - len(subvault_addresses),
)
supported_assets = await self._fetch_supported_assets()

asset_results = await asyncio.gather(
*[self.fetch_assets(addr) for addr in vault_addresses],
*[
self.fetch_assets(addr, supported_assets=supported_assets)
for addr in vault_addresses
],
return_exceptions=True,
)

Expand Down Expand Up @@ -242,9 +249,7 @@ async def _fetch_subvault_addresses(self) -> list[str]:
async def _fetch_supported_assets(self) -> list[str]:
"""Get the supported assets for the given vault."""
oracle_abi = load_oracle_abi()
oracle_address = get_oracle_address_from_vault(
self.config.vault_address_required, self.config.vault_rpc_required
)
oracle_address = get_oracle_address_from_vault(self.config)
raw_assets = await self._fetch_contract_list(
contract_address=oracle_address,
abi=oracle_abi,
Expand Down
2 changes: 0 additions & 2 deletions src/tq_oracle/adapters/check_adapters/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,9 @@
ActiveSubmitReportProposalCheck,
)

from tq_oracle.adapters.check_adapters.safe_state import SafeStateAdapter
from tq_oracle.adapters.check_adapters.timeout_check import TimeoutCheckAdapter

CHECK_ADAPTERS = [
SafeStateAdapter,
ActiveSubmitReportProposalCheck,
TimeoutCheckAdapter,
]
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,9 @@ async def _get_active_submit_report_proposals(self) -> list[object]:
safe_checksum = Web3.to_checksum_address(self._config.safe_address)

safe_api_url = f"{tx_service.base_url}/api/v1/safes/{safe_checksum}/"
safe_info_response = await asyncio.to_thread(requests.get, safe_api_url)
safe_info_response = await asyncio.to_thread(
requests.get, safe_api_url, timeout=10.0
)
safe_info_response.raise_for_status()
safe_info_data = safe_info_response.json()
current_nonce = int(safe_info_data.get("nonce", 0))
Expand Down
98 changes: 0 additions & 98 deletions src/tq_oracle/adapters/check_adapters/safe_state.py

This file was deleted.

13 changes: 10 additions & 3 deletions src/tq_oracle/adapters/check_adapters/timeout_check.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ async def run_check(self) -> CheckResult:
(
_price_d18,
last_report_timestamp,
_is_suspicious,
is_suspicious,
) = await oracle.functions.getReport(first_asset).call(
block_identifier=block_number
)
Expand All @@ -106,12 +106,19 @@ async def run_check(self) -> CheckResult:
block_identifier=block_number
)

latest_block = await w3.eth.get_block("latest")
current_timestamp = latest_block["timestamp"]
block_info = await w3.eth.get_block(block_number)
current_timestamp = block_info["timestamp"]

next_valid_time = last_report_timestamp + timeout
can_submit = current_timestamp >= next_valid_time

if is_suspicious:
return CheckResult(
passed=True,
message="Last report marked as suspicious, immediate replace allowed",
retry_recommended=False,
)

if can_submit:
return CheckResult(
passed=True,
Expand Down
5 changes: 4 additions & 1 deletion src/tq_oracle/adapters/price_adapters/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

from abc import ABC, abstractmethod
from dataclasses import dataclass
from dataclasses import field
from decimal import Decimal

from ...settings import OracleSettings

Expand All @@ -11,7 +13,8 @@ class PriceData:
"""Price data from a price adapter."""

base_asset: str
prices: dict[str, int] # asset_address -> price_wei (18 decimals)
prices: dict[str, Decimal] # asset_address -> price (D18) per base unit
decimals: dict[str, int] = field(default_factory=dict)


class BasePriceAdapter(ABC):
Expand Down
Loading
Loading