Problem
TokenMetaCache::build (crates/charon-scanner/src/token_meta.rs) eagerly fetches decimals() and symbol() for every Venus underlying at startup. A single transient HTTP 429 from the upstream RPC permanently flags that market as UNREACHABLE for the lifetime of the process:
ERROR charon_scanner::token_meta: symbol() failed — market is now UNREACHABLE by the profit gate
token=0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c <- WBNB
Once WBNB is unreachable, every vBNB-collateral position is silently dropped by the profit gate even though Venus' largest market is BNB.
Reproduction
Boot anvil fork against any free-tier RPC (dRPC default), no --compute-units-per-second throttle:
FORK_RPC=https://bsc.drpc.org FORK_BLOCK=latest ./scripts/anvil_fork.sh
RUST_LOG=info ./target/release/charon --config config/fork.toml listen
Inspect log — markets dropping to UNREACHABLE include WBNB, USDT, BTCB, ETH on a typical free-tier run. Even with the throttle from issue (anvil_fork.sh CUPS) added, 6+ markets are still lost on a single boot.
Impact
P1. Per-process market enumeration is non-deterministic and depends on RPC weather. WBNB loss alone makes the bot blind to vBNB collateral, which is the largest single Venus collateral pool. Mainnet liquidations against vBNB borrowers are unfindable until charon is restarted and the upstream is calmer.
Proposed fix
TokenMetaCache::build should:
- Retry transient failures. Exponential backoff (0.5s, 1s, 2s, 4s, 8s) for any error matching
code:-32603 HTTP 429 / code:35 (Too many request) / code:-32000 missing trie node. Cap at 5 attempts.
- Fail open via lazy fetch. A market that still failed after retries should not be marked
UNREACHABLE. Instead, leave it absent from the cache and try again on first use inside process_opportunity. The current eager-only design means any boot-time hiccup is catastrophic.
- Persist successful entries. Token decimals/symbol never change for an ERC-20. Cache to disk (
data/token_meta.json) so subsequent runs skip the fetch entirely.
Acceptance
- Repeated boot against the same free-tier RPC reliably maps all 48 Venus vToken underlyings within 60s of charon start.
- Single 429 burst no longer drops WBNB / USDT / BTCB / ETH for the rest of the process.
- Disk cache present after first successful run, used unconditionally on next start.
Found during the local mainnet validation walk on 2026-04-25.
Problem
TokenMetaCache::build(crates/charon-scanner/src/token_meta.rs) eagerly fetchesdecimals()andsymbol()for every Venus underlying at startup. A single transient HTTP 429 from the upstream RPC permanently flags that market asUNREACHABLEfor the lifetime of the process:Once
WBNBis unreachable, every vBNB-collateral position is silently dropped by the profit gate even though Venus' largest market is BNB.Reproduction
Boot anvil fork against any free-tier RPC (dRPC default), no
--compute-units-per-secondthrottle:Inspect log — markets dropping to
UNREACHABLEinclude WBNB, USDT, BTCB, ETH on a typical free-tier run. Even with the throttle from issue (anvil_fork.sh CUPS) added, 6+ markets are still lost on a single boot.Impact
P1. Per-process market enumeration is non-deterministic and depends on RPC weather. WBNB loss alone makes the bot blind to vBNB collateral, which is the largest single Venus collateral pool. Mainnet liquidations against vBNB borrowers are unfindable until charon is restarted and the upstream is calmer.
Proposed fix
TokenMetaCache::buildshould:code:-32603HTTP 429 /code:35(Too many request) /code:-32000 missing trie node. Cap at 5 attempts.UNREACHABLE. Instead, leave it absent from the cache and try again on first use insideprocess_opportunity. The current eager-only design means any boot-time hiccup is catastrophic.data/token_meta.json) so subsequent runs skip the fetch entirely.Acceptance
Found during the local mainnet validation walk on 2026-04-25.