AfterHours Oracle
A 24/7 price oracle for tokenized stocks on Robinhood Chain, live on mainnet: Chainlink while the market is open, the on-chain pool, bounded, while it is closed.
Videos




Tech Stack
Description
AfterHours is a price oracle for Robinhood Chain stock tokens that keeps answering while the US market is closed: a Stylus (Rust) contract with Chainlink's and Morpho's oracle interfaces.
Live on Robinhood Chain mainnet. AfterHours AAPL/USD runs at 0x88b628472e595725178cc3e5e2ec70ada67f80f0 (chain 4663), deployed on 2026-09-26. It is the oracle of a Morpho Blue market we opened: AAPL collateral, USDG loan, LLTV 62.5%, market id 0x3d9b0c04e374f7b50fa7a635393d2ecae23f45289e4e23f83793a6a611010918. It replaces our first deployment of 2026-09-25 (0x69190621e300cd2bc4cbb80777b517691ee80f65), which got the feed's unit wrong; see Smart contract quality.
Real problem, measured on Robinhood Chain mainnet. The chain trades tokenized US stocks around the clock, but their Chainlink feeds follow US market hours. The AAPL/USD feed went 52.2 to 56.8 hours without a print on ordinary weekends, and 76.2 hours over Labor Day. The token kept trading: the AAPL/USDG Uniswap pools cleared $5.1M, $4.2M, $2.0M and $0.58M over the four September weekends. Borrowing against these tokens has arrived. On 2026-09-30, 81 funded Morpho markets on the chain took a Robinhood stock token as collateral, with $756k supplied and $732k borrowed (97% utilization), up from $6.4k borrowed a week earlier. $300k of it was borrowed in one transaction on Sunday 09-27, while AAPL's feed had been silent since Friday (status/morpho-2026-09-30.txt). Their oracles keep Friday's price through the weekend or read a raw pool price with no bound.
Product. AfterHours is built to keep a stock token priced outside market hours. While the Chainlink feed is under six hours old, it returns the feed's round unchanged. After that it returns where the token trades on-chain: the middle of three 10-minute averages from one fixed Uniswap v3 pool, kept within 1% of the last exchange print during the US regular session while that print is under a day old, and within 10% otherwise. It refuses when the pool is too thin, the print is more than five days old, or the issuer flags a corporate action in progress (oraclePaused()). The current instance, read back by the deploy workflow at 22:50 UTC on Saturday 2026-09-26, was read every 10 minutes from 23:00 UTC through Monday's reopening (status/10min.md, 439 rows to Wednesday 00:00 UTC). While Friday's $341.4532 print aged, it answered from the pool, between $339.5466 and $340.9756, with no answer clamped or refused, and it went back to the feed at Monday's first print, $340.4326. Its last pool answer, $339.7844, was -0.190% from that print; Friday's print was +0.300% (scripts/measure/reopen_check.py). Over two weekday gaps in the feed the same comparison gave -0.534% against -0.583%, and +0.209% against +0.515%. The first instance's record from Friday evening (status/10min-0x6919-superseded.md) shows the switch itself: Friday's print until it was six hours old, then the pool from 01:50 UTC Saturday.
Smart contract quality. The contract has no owner and no upgrade path. initialize writes the configuration once; the deploy workflow first checks every read initialize will make, and afterwards reads every field back (it runs in our private working repository, of which the public one is a copy; scripts/probe.py re-reads the same fields). CI runs 73 unit and property tests, and the property runs reach every session and every refusal reason. The real wasm runs end to end on a local Nitro node at ArbOS 61 with 90 exact assertions, the deploy workflow's own steps included. One unit test serves the contract the real feed, pool and token answers from a mainnet block. Tick math is checked against 80-digit references. Eleven review rounds by separate AI agents are logged finding by finding in REVIEWS.md. A twelfth, our own check against Chainlink's documentation a day after the first deployment, found a real error: Chainlink's Robinhood feeds price one token, the share price times the token's dividend and split multiplier, and our contract applied that multiplier a second time in price() (0.057% for AAPL today, nine to ten times after a 10:1 split). We fixed the contract, re-ran the tests and the e2e suite, and redeployed with a new market; the first instance is marked superseded in the repository, and its market never held funds. The verify workflow rebuilds the deployed commit with cargo stylus verify and compares it with the deployment transaction; for the current instance it printed "Verification successful" (https://github.com/bongbongcrypto/afterhours/actions/runs/36297607270). Blockscout lists the contract as unverified because this chain's explorer has no Stylus verifier. On mainnet eth_estimateGas gives 118,426 gas for latestRoundData() and 120,519 for price() while the feed is fresh (block 76,073,475, 2026-09-30), about $0.0076-0.0080 a read, and 214,652-247,505 / 216,736-249,587 on the pool path (ten runs on 2026-09-26, one on 2026-09-30), about $0.015-0.017 (scripts/measure/mainnet_gas.py). Unaudited.
Product-market fit. The Morpho market above is priced by AfterHours through nights and weekends, so it can liquidate on a Saturday while the collateral falls on-chain instead of taking Monday's whole gap at once. Nobody uses AfterHours yet: the market has no deposits and no borrows (Morpho market() for this id, read 2026-09-30), and we know of no other integration. The $732k borrowed against stock tokens sits in the four largest markets, whose oracles answer Friday's print all weekend; the Sunday borrow above is the case AfterHours is for. AfterHours has Chainlink's AggregatorV3Interface, so a contract that reads the AAPL feed can switch by changing one address. Morpho fixes a market's oracle when the market is created, so a curator opens a new market with AfterHours, as we did. The same wasm serves any stock that qualifies. On 2026-09-23, 14 Robinhood stock tokens, NVDA, SPY, QQQ, MSFT and TSLA among them, had the feed, pool depth and observation history it needs; each takes one run of the deploy workflow.
Use of Arbitrum technology. Stylus lets the contract and its tests be the same Rust. The decision code runs unchanged under cargo test (3,000 random cases per CI run) and as wasm on chain; the 512-bit fixed-point math uses alloy's U512 with no assembly. Robinhood Chain is an Arbitrum chain on ArbOS 61 (read from its precompiles), and the e2e node is upgraded to the same version: the ~38 KB program deploys as two fragments, which needs ArbOS 61. Robinhood mainnet has no StylusDeployer factory, so a one-shot initialize replaces the constructor. A Stylus program's activation lasts 365 days on this chain, and anyone can renew it.
Interfaces. Chainlink consumers call latestRoundData() and the v2 getters. Morpho calls price(), the answer times 10^16: like Chainlink's Robinhood feeds, the answer prices one token of raw balance, so it stays continuous through dividends and splits. state() never reverts for market reasons and reports the session (LIVE_FEED, ONCHAIN_TWAP, PAUSED, NO_DATA), the reason, the pool price, its liquidity and whether the band clamped. Every number here traces to a read-only script in the repository, a CI run or a transaction in DEPLOYMENTS.md. The live page reads the chain from your browser, shows the instance's state() and checks it against the contract's rules run on the same block.
Progress During Hackathon
Measured the problem on Robinhood Chain mainnet: feed cadence, weekend swap flow across the four September weekends (78k swaps), pool depth and observation history, and every Morpho market on the chain with the oracle behind it.
Wrote the contract in Rust (Stylus SDK 0.10.9): session logic, Q96 tick math with 512-bit intermediates, price and liquidity judged over three sub-windows, two bands by the hour and the print's age, the anchor-age cap, the issuer pause flag, a fixed pricing venue, Chainlink (v3 + v2) and Morpho interfaces, and a one-shot initialize (Robinhood mainnet has no StylusDeployer factory).
73 unit and property tests; 90 on-chain assertions on a local ArbOS 61 node in CI; eleven review rounds and a self-review against Chainlink's documentation, every finding fixed or listed; CI runs fmt, clippy, tests and cargo stylus check against Robinhood testnet. A manual workflow deploys, activates, initializes and reads every field back.
Built the live page: it reads the feed, the pool and the token from the visitor's browser and applies the same rules with the same integer math.
Deployed the AAPL instance on Robinhood Chain mainnet on 2026-09-25 (0x69190621e300cd2bc4cbb80777b517691ee80f65, since superseded) and opened a Morpho Blue AAPL/USDG market for it. A server read it every 10 minutes: the feed's print on Friday evening, then answers from the pool from 01:50 UTC Saturday while the feed was silent (
status/10min-0x6919-superseded.md).Found our own unit error a day later, checking against Chainlink's documentation: the Robinhood feeds price one token, multiplier included, and the contract applied the multiplier again. Fixed the contract and tests (73 tests, 90 e2e assertions, both green), redeployed on 2026-09-26 at 0x88b628472e595725178cc3e5e2ec70ada67f80f0 with a new Morpho market, verified the build with cargo stylus verify, and marked the first instance superseded; its market never held funds. The server recorded the current instance every 10 minutes through the weekend and Monday's reopening (
status/10min.md, 439 rows): its last answer before Monday's first print was -0.190% from it, Friday's print +0.300% (scripts/measure/reopen_check.py). Mainnet gas per read is measured in both sessions.Re-ran every cited script on 09-30 before submitting. The Morpho census had outgrown the RPC's 10M-block log limit (it now reads in ranges and also lists each borrow with the session it fell in), and borrowing against stock tokens had grown from $6.4k to $732k; the texts, the live page and the video were updated to the new figures.
Fundraising Status
Bootstrapped; no funding raised or sought yet.